Skip to content

Commit 71db530

Browse files
author
GOURIOU Lénaïck
committed
test(robustness_tests): add new robustness tests for low-level bit manipulations (on BLS) on dlog and pedersen proofs
- flip LSB - add trailing bytes - flip bytes at the GroupEncoding index (also for Ristretto)
1 parent b610e7e commit 71db530

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed

src/fiat_shamir.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ where
180180
let commitment_size = self.ip.serialize_commitment(&commitment).len();
181181
let response = self.ip.deserialize_response(&proof[commitment_size..])?;
182182

183+
// Assert correct proof size
184+
let total_expected_len = commitment_size + self.ip.serialize_response(&response).len();
185+
if proof.len() != total_expected_len {
186+
return Err(Error::VerificationFailure);
187+
}
188+
183189
let mut hash_state = self.hash_state.clone();
184190

185191
// Recompute the challenge
@@ -243,6 +249,12 @@ where
243249
let challenge_size = self.ip.serialize_challenge(&challenge).len();
244250
let response = self.ip.deserialize_response(&proof[challenge_size..])?;
245251

252+
// Assert correct proof size
253+
let total_expected_len = challenge_size + self.ip.serialize_response(&response).len();
254+
if proof.len() != total_expected_len {
255+
return Err(Error::VerificationFailure);
256+
}
257+
246258
// Compute the commitments
247259
let commitment = self.ip.simulate_commitment(&challenge, &response)?;
248260
// Verify the proof

src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod composition;
22
mod morphism_edge_cases;
33
mod relations;
4+
mod robustness_tests;
45
mod spec;
56
pub mod test_utils;

src/tests/robustness_tests.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
use bls12_381::{G1Projective as G, Scalar};
2+
use curve25519_dalek::ristretto::RistrettoPoint;
3+
use curve25519_dalek::scalar::Scalar as DalekScalar;
4+
use group::ff::Field;
5+
use group::{Group, GroupEncoding};
6+
use rand::rngs::OsRng;
7+
8+
use crate::fiat_shamir::NISigmaProtocol;
9+
use crate::tests::test_utils::{discrete_logarithm, pedersen_commitment};
10+
use crate::{codec::ShakeCodec, schnorr_protocol::SchnorrProof};
11+
12+
/// This test checks that flipping a single low-order bit in the proof causes verification to fail.
13+
#[test]
14+
fn tampered_bitflip_proof() {
15+
let mut rng = OsRng;
16+
17+
let (morphismp, witness) = discrete_logarithm(Scalar::random(&mut rng));
18+
let protocol = SchnorrProof::from(morphismp);
19+
let nizk = NISigmaProtocol::<SchnorrProof<G>, ShakeCodec<G>>::new(
20+
b"tamper-test-bitflip-LSB",
21+
protocol,
22+
);
23+
24+
let proof_compact = nizk.prove_compact(&witness, &mut rng).unwrap();
25+
let proof_batchable = nizk.prove_batchable(&witness, &mut rng).unwrap();
26+
27+
// Flip the least significant bit of the first byte
28+
let mut tampered_compact = proof_compact.clone();
29+
let mut tampered_batchable = proof_batchable.clone();
30+
tampered_compact[0] ^= 0b00000001;
31+
tampered_batchable[0] ^= 0b00000001;
32+
33+
// Valid proofs should verify
34+
assert!(nizk.verify_compact(&proof_compact).is_ok());
35+
assert!(nizk.verify_batchable(&proof_batchable).is_ok());
36+
37+
// Tampered proofs should be rejected
38+
assert!(
39+
nizk.verify_compact(&tampered_compact).is_err(),
40+
"Compact proof with bitflip was incorrectly accepted"
41+
);
42+
assert!(
43+
nizk.verify_batchable(&tampered_batchable).is_err(),
44+
"Batchable proof with bitflip was incorrectly accepted"
45+
);
46+
}
47+
48+
/// This test checks that appending an extra byte invalidates the proof.
49+
#[test]
50+
fn tampered_extra_byte_proof() {
51+
let mut rng = OsRng;
52+
53+
let (morphismp, witness) = discrete_logarithm(Scalar::random(&mut rng));
54+
let protocol = SchnorrProof::from(morphismp);
55+
let nizk =
56+
NISigmaProtocol::<SchnorrProof<G>, ShakeCodec<G>>::new(b"tamper-test-extra-byte", protocol);
57+
58+
let proof_compact = nizk.prove_compact(&witness, &mut rng).unwrap();
59+
let proof_batchable = nizk.prove_batchable(&witness, &mut rng).unwrap();
60+
61+
// Append a null byte at the end of the proof
62+
let mut tampered_compact = proof_compact.clone();
63+
let mut tampered_batchable = proof_batchable.clone();
64+
tampered_compact.push(0x00);
65+
tampered_batchable.push(0x00);
66+
67+
// Valid proofs should verify
68+
assert!(nizk.verify_compact(&proof_compact).is_ok());
69+
assert!(nizk.verify_batchable(&proof_batchable).is_ok());
70+
71+
// Tampered proofs should be rejected due to unexpected length
72+
assert!(
73+
nizk.verify_compact(&tampered_compact).is_err(),
74+
"Compact proof with extra byte was incorrectly accepted"
75+
);
76+
assert!(
77+
nizk.verify_batchable(&tampered_batchable).is_err(),
78+
"Batchable proof with extra byte was incorrectly accepted"
79+
);
80+
}
81+
82+
/// This test checks that flipping the high bit in a group encoding breaks the proof (Bls12_381 backend).
83+
#[test]
84+
fn tampered_flip_high_bit_in_group_element() {
85+
let mut rng = OsRng;
86+
87+
let (morphismp, witness) = discrete_logarithm(Scalar::random(&mut rng));
88+
let protocol = SchnorrProof::from(morphismp);
89+
let nizk = NISigmaProtocol::<SchnorrProof<G>, ShakeCodec<G>>::new(
90+
b"tamper-test-curve-encoding",
91+
protocol,
92+
);
93+
94+
let proof_compact = nizk.prove_compact(&witness, &mut rng).unwrap();
95+
let proof_batchable = nizk.prove_batchable(&witness, &mut rng).unwrap();
96+
97+
let mut tampered_compact = proof_compact.clone();
98+
let mut tampered_batchable = proof_batchable.clone();
99+
100+
// Determine size of a compressed group element (48 bytes for BLS12-381 G1)
101+
let point_size = <G as GroupEncoding>::Repr::default().as_ref().len();
102+
103+
// Flip the most significant bit of the last byte in the first group element
104+
tampered_batchable[point_size - 1] ^= 0b10000000;
105+
106+
// Flip the MSB of the first byte in the scalar challenge
107+
tampered_compact[0] ^= 0b10000000;
108+
109+
// Valid proofs should verify
110+
assert!(nizk.verify_compact(&proof_compact).is_ok());
111+
assert!(nizk.verify_batchable(&proof_batchable).is_ok());
112+
113+
// Tampered proofs should fail due to invalid group or scalar encoding
114+
assert!(
115+
nizk.verify_compact(&tampered_compact).is_err(),
116+
"Compact proof with MSB flipped was incorrectly accepted"
117+
);
118+
assert!(
119+
nizk.verify_batchable(&tampered_batchable).is_err(),
120+
"Batchable proof with MSB flipped was incorrectly accepted"
121+
);
122+
}
123+
124+
/// This test checks that flipping the high bit in a group encoding breaks the proof (Ristretto backend).
125+
#[test]
126+
fn tampered_flip_high_bit_in_group_element_ristretto() {
127+
let mut rng = OsRng;
128+
129+
let (morphismp, witness) = discrete_logarithm(DalekScalar::random(&mut rng));
130+
let protocol = SchnorrProof::from(morphismp);
131+
let nizk = NISigmaProtocol::<SchnorrProof<RistrettoPoint>, ShakeCodec<RistrettoPoint>>::new(
132+
b"tamper-ristretto-msb",
133+
protocol,
134+
);
135+
136+
let proof_compact = nizk.prove_compact(&witness, &mut rng).unwrap();
137+
let proof_batchable = nizk.prove_batchable(&witness, &mut rng).unwrap();
138+
139+
// Clone for tampering
140+
let mut tampered_compact = proof_compact.clone();
141+
let mut tampered_batchable = proof_batchable.clone();
142+
143+
// Ristretto group encoding size is 32 bytes
144+
let point_size = <RistrettoPoint as GroupEncoding>::Repr::default()
145+
.as_ref()
146+
.len();
147+
148+
// Tamper: Flip MSB of last byte in first group element
149+
tampered_batchable[point_size - 1] ^= 0b10000000;
150+
151+
// Tamper: Flip MSB of first byte of scalar
152+
tampered_compact[0] ^= 0b10000000;
153+
154+
// Check original proofs still pass
155+
assert!(nizk.verify_compact(&proof_compact).is_ok());
156+
assert!(nizk.verify_batchable(&proof_batchable).is_ok());
157+
158+
// Check tampered proofs fail due to invalid encoding
159+
assert!(
160+
nizk.verify_compact(&tampered_compact).is_err(),
161+
"Compact Ristretto proof with MSB flipped was incorrectly accepted"
162+
);
163+
assert!(
164+
nizk.verify_batchable(&tampered_batchable).is_err(),
165+
"Batchable Ristretto proof with MSB flipped was incorrectly accepted"
166+
);
167+
}
168+
169+
#[test]
170+
fn tampered_bitflip_pedersen_proof() {
171+
let mut rng = OsRng;
172+
173+
let (morphismp, witness) = pedersen_commitment(
174+
G::random(&mut rng),
175+
Scalar::random(&mut rng),
176+
Scalar::random(&mut rng),
177+
);
178+
let protocol = SchnorrProof::from(morphismp);
179+
let nizk = NISigmaProtocol::<SchnorrProof<G>, ShakeCodec<G>>::new(
180+
b"tamper-pedersen-bitflip",
181+
protocol,
182+
);
183+
184+
let proof_compact = nizk.prove_compact(&witness, &mut rng).unwrap();
185+
let proof_batchable = nizk.prove_batchable(&witness, &mut rng).unwrap();
186+
187+
// Tampering: Flip 1 LSB in the first byte
188+
let mut tampered_compact = proof_compact.clone();
189+
let mut tampered_batchable = proof_batchable.clone();
190+
tampered_compact[0] ^= 0b00000001;
191+
tampered_batchable[0] ^= 0b00000001;
192+
193+
assert!(nizk.verify_compact(&proof_compact).is_ok());
194+
assert!(nizk.verify_batchable(&proof_batchable).is_ok());
195+
196+
assert!(
197+
nizk.verify_compact(&tampered_compact).is_err(),
198+
"Bit-flipped compact Pedersen proof was incorrectly accepted"
199+
);
200+
assert!(
201+
nizk.verify_batchable(&tampered_batchable).is_err(),
202+
"Bit-flipped batchable Pedersen proof was incorrectly accepted"
203+
);
204+
}
205+
206+
#[test]
207+
fn tampered_extra_byte_pedersen_proof() {
208+
let mut rng = OsRng;
209+
210+
let (morphismp, witness) = pedersen_commitment(
211+
G::random(&mut rng),
212+
Scalar::random(&mut rng),
213+
Scalar::random(&mut rng),
214+
);
215+
let protocol = SchnorrProof::from(morphismp);
216+
let nizk = NISigmaProtocol::<SchnorrProof<G>, ShakeCodec<G>>::new(
217+
b"tamper-pedersen-extra-byte",
218+
protocol,
219+
);
220+
221+
let proof_compact = nizk.prove_compact(&witness, &mut rng).unwrap();
222+
let proof_batchable = nizk.prove_batchable(&witness, &mut rng).unwrap();
223+
224+
// Tampering: Add a trailing null byte
225+
let mut tampered_compact = proof_compact.clone();
226+
let mut tampered_batchable = proof_batchable.clone();
227+
tampered_compact.push(0x00);
228+
tampered_batchable.push(0x00);
229+
230+
assert!(nizk.verify_compact(&proof_compact).is_ok());
231+
assert!(nizk.verify_batchable(&proof_batchable).is_ok());
232+
233+
assert!(
234+
nizk.verify_compact(&tampered_compact).is_err(),
235+
"Compact Pedersen proof with extra byte was incorrectly accepted"
236+
);
237+
assert!(
238+
nizk.verify_batchable(&tampered_batchable).is_err(),
239+
"Batchable Pedersen proof with extra byte was incorrectly accepted"
240+
);
241+
}
242+
243+
#[test]
244+
fn tampered_flip_high_bit_in_pedersen_group_element() {
245+
let mut rng = OsRng;
246+
247+
let (morphismp, witness) = pedersen_commitment(
248+
G::random(&mut rng),
249+
Scalar::random(&mut rng),
250+
Scalar::random(&mut rng),
251+
);
252+
let protocol = SchnorrProof::from(morphismp);
253+
let nizk = NISigmaProtocol::<SchnorrProof<G>, ShakeCodec<G>>::new(
254+
b"tamper-pedersen-msb-flip",
255+
protocol,
256+
);
257+
258+
let proof_compact = nizk.prove_compact(&witness, &mut rng).unwrap();
259+
let proof_batchable = nizk.prove_batchable(&witness, &mut rng).unwrap();
260+
261+
// Tampering: Flip MSB of one group element and one scalar
262+
let mut tampered_compact = proof_compact.clone();
263+
let mut tampered_batchable = proof_batchable.clone();
264+
265+
let point_size = <G as GroupEncoding>::Repr::default().as_ref().len();
266+
tampered_batchable[point_size - 1] ^= 0b10000000;
267+
tampered_compact[0] ^= 0b10000000;
268+
269+
assert!(nizk.verify_compact(&proof_compact).is_ok());
270+
assert!(nizk.verify_batchable(&proof_batchable).is_ok());
271+
272+
assert!(
273+
nizk.verify_compact(&tampered_compact).is_err(),
274+
"Compact Pedersen proof with MSB flipped was incorrectly accepted"
275+
);
276+
assert!(
277+
nizk.verify_batchable(&tampered_batchable).is_err(),
278+
"Batchable Pedersen proof with MSB flipped was incorrectly accepted"
279+
);
280+
}

0 commit comments

Comments
 (0)