Skip to content

Commit 11a526e

Browse files
committed
Add DST to bulletproofs, DDH and key consistency proofs
Adds a domain-separation-tag (DST) parameter to the range proofs (bulletproofs) and the sigma-protocol proofs used by Contra: - RangeProof prove/verify bind the DST into the Merlin transcript. - DdhTupleNizk (nizk.rs) and ZeroProof / ConsistencyProof / KeyConsistencyProof (twisted_elgamal.rs) bind the DST into their Fiat-Shamir challenge. The DDH, ElGamal and key-consistency challenge constructions are made byte-for-byte compatible with the Move and TypeScript implementations in Contra: blake2b256(dst || canonical-element-bytes... in Move's order) with the top byte zeroed and reduced to a canonical little-endian scalar, exposed as RistrettoScalar::fiat_shamir_challenge. The DDH challenge reduction is abstracted behind a FiatShamirChallenge strategy trait (default Blake2bCanonicalChallenge) so alternative reductions can be plugged in; ZeroProof and KeyConsistencyProof reuse the same strategy.
1 parent 239eed7 commit 11a526e

5 files changed

Lines changed: 378 additions & 150 deletions

File tree

fastcrypto/benches/bulletproofs.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ fn verification(c: &mut Criterion) {
2323
.iter()
2424
.map(|&v| PedersenCommitment::commit(&RistrettoScalar::from(v), &mut rng))
2525
.unzip();
26-
let proof = RangeProof::prove_batch(&values, &blindings, range, &mut rng).unwrap();
26+
let proof =
27+
RangeProof::prove_batch(&values, &blindings, range, b"bench", &mut rng).unwrap();
2728
c.bench_function(
2829
&format!("bulletproofs/verification/range={}/inputs={}", bits, size),
2930
|b| {
30-
b.iter(|| proof.verify_batch(&commitments, range, &mut rng));
31+
b.iter(|| proof.verify_batch(&commitments, range, b"bench", &mut rng));
3132
},
3233
);
3334
}

fastcrypto/src/bulletproofs.rs

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
//! let mut rng = rand::thread_rng();
1616
//! let blinding = Blinding::rand(&mut rng);
1717
//! let proof =
18-
//! RangeProof::prove(value, &blinding, &range, &mut rng).unwrap();
18+
//! RangeProof::prove(value, &blinding, &range, b"dst", &mut rng).unwrap();
1919
//! let commitment = PedersenCommitment::new(&RistrettoScalar::from(value), &blinding);
20-
//! assert!(proof.verify(&commitment, &range, &mut rng).is_ok());
20+
//! assert!(proof.verify(&commitment, &range, b"dst", &mut rng).is_ok());
2121
//! ```
2222
2323
use crate::error::FastCryptoError::{GeneralOpaqueError, InvalidInput, InvalidProof};
@@ -71,19 +71,21 @@ impl RangeProof {
7171
value: u64,
7272
blinding: &Blinding,
7373
range: &Range,
74+
dst: &[u8],
7475
rng: &mut impl AllowedRng,
7576
) -> FastCryptoResult<RangeProof> {
76-
Self::prove_batch(&[value], std::slice::from_ref(blinding), range, rng)
77+
Self::prove_batch(&[value], std::slice::from_ref(blinding), range, dst, rng)
7778
}
7879

7980
/// Verifies a range proof: That the commitment is to a value in the given range.
8081
pub fn verify(
8182
&self,
8283
commitment: &PedersenCommitment,
8384
range: &Range,
85+
dst: &[u8],
8486
rng: &mut impl AllowedRng,
8587
) -> FastCryptoResult<()> {
86-
self.verify_batch(std::slice::from_ref(commitment), range, rng)
88+
self.verify_batch(std::slice::from_ref(commitment), range, dst, rng)
8789
}
8890

8991
/// Create a proof that all the given `values` are in the range using the given commitment blindings.
@@ -97,6 +99,7 @@ impl RangeProof {
9799
values: &[u64],
98100
blindings: &[Blinding],
99101
range: &Range,
102+
dst: &[u8],
100103
rng: &mut impl AllowedRng,
101104
) -> FastCryptoResult<RangeProof> {
102105
if values.iter().any(|&v| !range.is_in_range(v))
@@ -109,6 +112,7 @@ impl RangeProof {
109112
let bits = range.upper_bound_in_bits() as usize;
110113
let bp_gens = BulletproofGens::new(bits, values.len());
111114
let mut prover_transcript = Transcript::new(&[]);
115+
prover_transcript.append_message(b"DST", dst);
112116

113117
// TODO: Can we avoid calculating the Pedersen commitments here?
114118
ExternalRangeProof::prove_multiple_with_rng(
@@ -129,11 +133,13 @@ impl RangeProof {
129133
&self,
130134
commitments: &[PedersenCommitment],
131135
range: &Range,
136+
dst: &[u8],
132137
rng: &mut impl AllowedRng,
133138
) -> FastCryptoResult<()> {
134139
let bits = range.upper_bound_in_bits() as usize;
135140
let bp_gens = BulletproofGens::new(bits, commitments.len());
136141
let mut verifier_transcript = Transcript::new(&[]);
142+
verifier_transcript.append_message(b"DST", dst);
137143

138144
self.0
139145
.verify_multiple_with_rng(
@@ -188,9 +194,9 @@ fn test_range_proof_valid() {
188194
let blinding = Blinding::rand(&mut rng);
189195

190196
let value = 1u64;
191-
let proof = RangeProof::prove(value, &blinding, &range, &mut rng).unwrap();
197+
let proof = RangeProof::prove(value, &blinding, &range, b"test", &mut rng).unwrap();
192198
let commitment = PedersenCommitment::new(&RistrettoScalar::from(value), &blinding);
193-
assert!(proof.verify(&commitment, &range, &mut rng).is_ok());
199+
assert!(proof.verify(&commitment, &range, b"test", &mut rng).is_ok());
194200
}
195201

196202
#[test]
@@ -203,15 +209,16 @@ fn test_batch_range_proof_valid() {
203209
.iter()
204210
.map(|&v| PedersenCommitment::commit(&RistrettoScalar::from(v), &mut rng))
205211
.unzip::<_, _, Vec<_>, Vec<_>>();
206-
let proof = RangeProof::prove_batch(&values, &blindings, &range, &mut rng).unwrap();
207-
assert!(proof.verify_batch(&commitments, &range, &mut rng).is_ok());
212+
let proof =
213+
RangeProof::prove_batch(&values, &blindings, &range, b"test_dst", &mut rng).unwrap();
214+
assert!(proof
215+
.verify_batch(&commitments, &range, b"test_dst", &mut rng)
216+
.is_ok());
208217
}
209218

210219
#[test]
211220
fn test_to_from_bytes() {
212-
use crate::encoding::{Encoding, Hex};
213221
use crate::groups::ristretto255::RistrettoScalar;
214-
use crate::serde_helpers::ToFromByteArray;
215222

216223
let range = Range::Bits32;
217224
let mut rng = rand::thread_rng();
@@ -220,15 +227,11 @@ fn test_to_from_bytes() {
220227
.iter()
221228
.map(|&v| PedersenCommitment::commit(&RistrettoScalar::from(v), &mut rng))
222229
.unzip::<_, _, Vec<_>, Vec<_>>();
223-
commitments
224-
.iter()
225-
.for_each(|c| println!("{}", Hex::encode(c.0.to_byte_array())));
226-
let proof = RangeProof::prove_batch(&values, &blindings, &range, &mut rng).unwrap();
230+
let proof = RangeProof::prove_batch(&values, &blindings, &range, b"test", &mut rng).unwrap();
227231
let proof_bytes = proof.to_bytes();
228-
println!("{}", Hex::encode(&proof_bytes));
229232
let reconstructed_proof = RangeProof::from_bytes(&proof_bytes).unwrap();
230233
assert!(reconstructed_proof
231-
.verify_batch(&commitments, &range, &mut rng)
234+
.verify_batch(&commitments, &range, b"test", &mut rng)
232235
.is_ok());
233236
}
234237

@@ -239,23 +242,28 @@ fn regression_test() {
239242
use crate::pedersen::PedersenCommitment;
240243
use crate::serde_helpers::ToFromByteArray;
241244

242-
let proof = RangeProof::from_bytes(&Hex::decode("483752e4e9be898bb7098adbe1cd4ec71614c7cf897de2d9e6b5d7615bb96b7b9ca3621b3fd885045903b82a4bc979094639f216c479362b0ab497dcbbe5600580c3a9e90d1fab586d7f3e8c9fcae71da9474c106610b5d473824a33e3472e20e04390d7052480b3500a96167e405a9b27f59a6b74f47457454014307a97e108ab6681983e5556fb19d2a33f753b364695cb7188948ff7f997a5fe3d55fcc902fcd5afcbd45a41ef298d6e16560fb407d29c81dac20705cd18d52f4925700003bf7b175cdf6d5d8c6eeef35f7357a88edac323eef8dda1f90ff4ebafab91650182d44889691ed4e1789891d7111c2adc97feb2e48e5bde1d3407f98e2a85b66c086bc4d03cbdd9237023bd601bbad70377345bafe027d2e794d702585a7ce72008cb2efec10638555841eb17917c1b6d41533e5a93496fbb2c8cf1e98830732290dd8d6b6a10c2622d866c6e0dbac2aee6c94409f5a50affbc47081cd24414414e080474c144f2b2e38d8c02a56c9dc8bc5ca6b342cfbec04c40b9e8225a7f1fb62b3ddc4ad00f7606ce2942f123503da272e2a6e25a6f0d0038030b489c8675d614bc0073056ffa2ec10231c50b7fcae9d961f66dd299da4b1985a2eb6c215aee1e41de609e70ffcf73bd2b1c677e7afe31c0d16c202dbc006f187266f94f2be44668778c18f0b76f9cb4608594350371c2e0391b6be322fbb78fa03400f272ecae55d95c3ddccfc76353753fa596ef076ac02084a9bcc525df5e5ab086850868c17b907c484553e19fd693702884c4745eb833bd27598a23ef0e68828c934b7a0a8f66e811bede8bf8a4e141a8f12f964e92e76d5af2b3e9f76792b935fc0d8cd906e7ab4f9e6a3bd9b826c0503a929634e7cdf8fdf5173772e372bb19862072ed0fb3afb44cae8b79caad81370ce67e4a1b44b8a56150219ca6982cf21c2fd80b4f8a675e585456223f60a96574c0972aa5f1ae68e04d9cf5b6e635f0903c3efe47d4c0c0d8d656d14928397d4e20806b50fb945d8c5b3af7b9c3b06dab726a41897ba3e94531eba51f7c16e390709a5efe407255dcfc40d86a1eb0fa4f01adee93fa6ed44a9ad03ba83b7d09f88bb4dbb51d8ef4b73b36b75dcaae04890a").unwrap()).unwrap();
245+
let proof = RangeProof::from_bytes(&Hex::decode("e27e1f3db57f05833973ec4b3e9def2b660ef256e42be0ca2747d47ff1ccdb30e65730ffed8175c640a8bfaa6d8840baa74130b439e9270dcbb0ce041801040a00d0d7dcdcd088f6bb63320ec2c1c2db4b5634ec7b477c2685a390a76658a679809d44e5a62481597a843a0d93953ebc9dc0b4640a02408111e7ad82da6c7460334eb71999b7758f27ea76f76b5335b21398574525e04b1d609eca69080f250cd573edf050ca9cb0d152d493037b2f46b0d2a8db005ec2bb13cfc67829fd220c989e3e3b4ba93bd798ff45892f69b279bc3311ed19ab396c8ce7a81d314ef10ef6b7ce7d0d323ae8e03c69e38bc3dfc88f4930d69adc8661e577f7bcd10c7b3d0c2dd337a9149a90ef961b0ac7f39b43a9658698036bb94ec2fa30de9058c16e20a6a88b60adf0af37e01f90a51bf3353256f4ec10d862b1870b58dd4f0cea255891878a36e54ebbfc126d271e54459abcd74b1b05512e43ae8cd8df88d96159ae6e0b0dda3e0da88c8aad392d264e34c9cc40464d3cb24f39a25373104ff03cb45050dc9d65d5909a07bbbee45e875d48aa3e1355d7356a97d7d8a37fa88355b0115b8eeda7e2dbae17c4259b9a75d93830e9934781275e771263313824bb4f8439f882fba97f4bb8f5159a22640181d155f8ca342055654325a6d6ae25460d4075e595f8eb1cde6afd95a0c1eec6edcfc60c666501bfbb38098faec118672f622a484e848e1fc523bf6b09f4f03324ae2bc66f9eeffa4d9fd86392c1602b4d1e3fafd9db0f5d263cfdc98f7d65ad4cd6d765e7c151d7cd528839f4d6b8bd481203e7e5c3fcfa114f86bd71240b8a4e7efa760f6691d2d23120957d258915379a2bf98c4bb3c011e704ebdeb463dd53bbd3e6ca01a1dc070e4064b73e4d690a76ced51e0b0562783409c8c8cdff0864f7636641481562aaeb1c2c962ecd2a5e3e2b376ee5f0b1bcad29f0e3cd292144455c63b708154bca4f80d337f06bfe5f520750afaf6d2fa78e400a6bbf1625e8b262ea24539237fd365b4dd1fdc9644b2752bf6e6c0aa3bdd8332ebf9f34c32176e4fea97a5a185dd267e11f6283e20dce2a83e4fc1e1e848d6e1b3174564aa3b5eb9c523f35f1274b54da5a2a6d7905").unwrap()).unwrap();
243246
let commitments = [
244-
"7c7ea4aadabb7162ed05f265527c50fb1a441b0ca655231bf6d7c45d6cf67e1d",
245-
"702b85154cd2dd7d0505af8cc6484f0aa854549e64b87f07277af70d995a2d21",
246-
"6693d794456c40fbcaaf0692c376aca4ba168782a6b76df49100b8d766a73b5c",
247-
"1a7ff5f0f877da164da5630b280278e03e75cb19ed1469b1853df3fb8050a662",
248-
"e25bae601d7113cd0db0603fe7072e88f5aeae6177e423bf2e22abf5f997ef6b",
249-
"b6d98f2eae41a89e49fa2381a7b88517bfbe0fdebb656712e2e9d6f50df1720c",
250-
"e2b788992088af1a3f9810a4c85fc4bfa4fc48e2cfcc3e546deb030e47e6cc7a",
251-
"60d05a6cdd77f71b15356fb830eb3a5a26b79b2870df6a39dd704490e0e6162d",
247+
"3864fd028e266349c38e76d2b3361e1a197084247dd9c7413c061bcbd2026634",
248+
"0e95477497ebd8390b70fde21813ad0f060d2b5fe8ee300f045d5dc4941fd431",
249+
"74a2b02427dd95fbe2e0fdc7c1fb046ad1bfec0cb7777581ccc2bc3fcb81462f",
250+
"d65e1514458c92150f02ef24d9524490f26bdbba9302e7e06056007576bf8e44",
251+
"a27f94f3d348cb67237a0e86beba42a20d6cd36b63ce7421e5a8fd2043f7727e",
252+
"00f28f68b3dad813674a578644391e4faa272a6d29522eb38e1773c72cfed145",
253+
"6afc2e4d5d9924b8359a25cefe49a04ea8b1cab5b03c17d535a69b4603350e52",
254+
"d0983791c005d212e98659c181e72f187680f2675b163dc817d8dc41d03b3c7c",
252255
]
253256
.iter()
254257
.map(|s| Hex::decode(s).unwrap())
255258
.map(|b| RistrettoPoint::from_byte_array(&b.try_into().unwrap()).unwrap())
256259
.map(PedersenCommitment)
257260
.collect::<Vec<_>>();
258261
assert!(proof
259-
.verify_batch(&commitments, &Range::Bits32, &mut rand::thread_rng())
262+
.verify_batch(
263+
&commitments,
264+
&Range::Bits32,
265+
b"test",
266+
&mut rand::thread_rng()
267+
)
260268
.is_ok());
261269
}

fastcrypto/src/groups/ristretto255.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::groups::{
1010
Doubling, FiatShamirChallenge, FromTrustedByteArray, GroupElement, HashToGroupElement,
1111
MultiScalarMul, Scalar,
1212
};
13-
use crate::hash::Sha512;
13+
use crate::hash::{Blake2b256, Sha512};
1414
use crate::serde_helpers::ToFromByteArray;
1515
use crate::traits::AllowedRng;
1616
use crate::{
@@ -131,7 +131,6 @@ impl RistrettoScalar {
131131
pub fn from_bytes_mod_order_wide(bytes: &[u8; 64]) -> Self {
132132
RistrettoScalar(ExternalScalar::from_bytes_mod_order_wide(bytes))
133133
}
134-
135134
/// Construct a [RistrettoScalar] by reducing a 32-byte little-endian integer modulo the group order.
136135
pub fn from_bytes_mod_order(bytes: &[u8; 32]) -> Self {
137136
RistrettoScalar(ExternalScalar::from_bytes_mod_order(*bytes))
@@ -200,7 +199,10 @@ impl HashToGroupElement for RistrettoScalar {
200199

201200
impl FiatShamirChallenge for RistrettoScalar {
202201
fn fiat_shamir_reduction_to_group_element(msg: &[u8]) -> Self {
203-
Self::hash_to_group_element(msg)
202+
// Matches Contra's Move/TS Fiat-Shamir construction.
203+
let mut digest = Blake2b256::digest(msg).digest;
204+
digest[RISTRETTO_SCALAR_BYTE_LENGTH - 1] = 0;
205+
Self::from_byte_array(&digest).expect("Top byte is zero so the scalar is always canonical")
204206
}
205207
}
206208

fastcrypto/src/nizk.rs

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
use crate::error::{FastCryptoError, FastCryptoResult};
55
use crate::groups::MultiScalarMul;
66
use crate::groups::{FiatShamirChallenge, GroupElement, Scalar};
7-
use crate::hash::{HashFunction, Sha3_256};
87
use crate::traits::AllowedRng;
98
use serde::{Deserialize, Deserializer, Serialize};
109
use std::ops::Neg;
@@ -28,31 +27,34 @@ pub struct DdhTupleNizk<G: GroupElement + Serialize + for<'d> Deserialize<'d>> {
2827
impl<G> DdhTupleNizk<G>
2928
where
3029
G: MultiScalarMul + Serialize + for<'d> Deserialize<'d>,
31-
<G as GroupElement>::ScalarType: FiatShamirChallenge,
30+
G::ScalarType: FiatShamirChallenge,
3231
{
3332
/// Create a new NIZKPoK for the DDH tuple `(G, H=eG, xG, xH)` using the given RNG.
33+
/// The generators `g` and `h` are not bound into the challenge, so the soundness relies
34+
/// on `dst` being unique to the calling context and fixed with the choice of `g` and `h`.
3435
pub fn create<R: AllowedRng>(
3536
x: &G::ScalarType,
3637
g: &G,
3738
h: &G,
3839
x_g: &G,
3940
x_h: &G,
41+
dst: &[u8],
4042
rng: &mut R,
4143
) -> Self {
4244
let r = G::ScalarType::rand(rng);
4345
let a = *g * r;
4446
let b = *h * r;
45-
let challenge = Self::fiat_shamir_challenge(g, h, x_g, x_h, &a, &b);
47+
let challenge = Self::challenge(x_g, x_h, &a, &b, dst);
4648
let z = challenge * x + r;
4749
DdhTupleNizk { a, b, z }
4850
}
4951

50-
/// Verify this NIZKPoK.
51-
pub fn verify(&self, g: &G, h: &G, x_g: &G, x_h: &G) -> FastCryptoResult<()> {
52+
/// Verify this NIZKPoK. `dst` must match the value used in [Self::create].
53+
pub fn verify(&self, g: &G, h: &G, x_g: &G, x_h: &G, dst: &[u8]) -> FastCryptoResult<()> {
5254
if *g == G::zero() || *h == G::zero() || *x_g == G::zero() || *x_h == G::zero() {
5355
return Err(FastCryptoError::InvalidProof);
5456
}
55-
let challenge = Self::fiat_shamir_challenge(g, h, x_g, x_h, &self.a, &self.b);
57+
let challenge = Self::challenge(x_g, x_h, &self.a, &self.b, dst);
5658
if !is_valid_relation(&self.a, x_g, g, &self.z, &challenge)
5759
|| !is_valid_relation(
5860
&self.b, // B
@@ -65,10 +67,20 @@ where
6567
}
6668
}
6769

68-
/// Returns the challenge for Fiat-Shamir.
69-
fn fiat_shamir_challenge(g: &G, h: &G, x_g: &G, x_h: &G, a: &G, b: &G) -> G::ScalarType {
70-
let output = Sha3_256::digest(bcs::to_bytes(&(g, h, x_g, x_h, a, b)).unwrap());
71-
G::ScalarType::fiat_shamir_reduction_to_group_element(&output.digest)
70+
/// DDH-tuple Fiat-Shamir challenge: bcs-encoded `vector<vector<u8>>` of `[dst, xG, xH, A, B]`
71+
/// reduced via the scalar's [FiatShamirChallenge] impl. For Ristretto255 this matches Contra's
72+
/// Move/TS construction.
73+
fn challenge(x_g: &G, x_h: &G, a: &G, b: &G, dst: &[u8]) -> G::ScalarType {
74+
let chunks: Vec<Vec<u8>> = vec![
75+
dst.to_vec(),
76+
bcs::to_bytes(x_g).expect("Serialization succeeds"),
77+
bcs::to_bytes(x_h).expect("Serialization succeeds"),
78+
bcs::to_bytes(a).expect("Serialization succeeds"),
79+
bcs::to_bytes(b).expect("Serialization succeeds"),
80+
];
81+
G::ScalarType::fiat_shamir_reduction_to_group_element(
82+
&bcs::to_bytes(&chunks).expect("Serialization succeeds"),
83+
)
7284
}
7385
}
7486

@@ -107,80 +119,88 @@ mod tests {
107119

108120
#[test]
109121
fn test_nizk_flow() {
122+
let dst = b"test";
110123
let e = S::rand(&mut thread_rng());
111124
let x = S::rand(&mut thread_rng());
112125
let g = G::generator() * S::rand(&mut thread_rng());
113126
let h = g * e;
114127
let x_g = g * x;
115128
let x_h = h * x;
116-
let nizk = DdhTupleNizk::create(&x, &g, &h, &x_g, &x_h, &mut thread_rng());
117-
assert!(nizk.verify(&g, &h, &x_g, &x_h).is_ok());
129+
let nizk = DdhTupleNizk::create(&x, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
130+
assert!(nizk.verify(&g, &h, &x_g, &x_h, dst).is_ok());
131+
132+
// A different DST must not verify
133+
assert!(nizk.verify(&g, &h, &x_g, &x_h, b"other").is_err());
118134

119135
let invalid_witness = x + S::generator();
120136
let invalid_nizk =
121-
DdhTupleNizk::create(&invalid_witness, &g, &h, &x_g, &x_h, &mut thread_rng());
122-
assert!(invalid_nizk.verify(&g, &h, &x_g, &x_h).is_err());
137+
DdhTupleNizk::create(&invalid_witness, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
138+
assert!(invalid_nizk.verify(&g, &h, &x_g, &x_h, dst).is_err());
123139

124140
let other_g = g + G::generator();
125-
assert!(nizk.verify(&other_g, &h, &x_g, &x_h).is_err());
141+
assert!(nizk.verify(&other_g, &h, &x_g, &x_h, dst).is_err());
126142

127143
let other_h = h + G::generator();
128-
assert!(nizk.verify(&g, &other_h, &x_g, &x_h).is_err());
144+
assert!(nizk.verify(&g, &other_h, &x_g, &x_h, dst).is_err());
129145

130146
let other_x_g = x_g + G::generator();
131-
assert!(nizk.verify(&g, &h, &other_x_g, &x_h).is_err());
147+
assert!(nizk.verify(&g, &h, &other_x_g, &x_h, dst).is_err());
132148

133149
let other_x_h = x_h + G::generator();
134-
assert!(nizk.verify(&g, &h, &x_g, &other_x_h).is_err());
150+
assert!(nizk.verify(&g, &h, &x_g, &other_x_h, dst).is_err());
135151
}
136152

137153
#[test]
138154
fn challenge_regression_test() {
139-
let e = S::from(7u64);
155+
let dst = b"test";
140156
let x = S::from(31u64);
141157
let g = G::generator() * S::from(71u64);
142-
let h = g * e;
143158
let x_g = g * x;
144-
let x_h = h * x;
159+
let x_h = g * S::from(7u64) * x;
145160
let r = S::from(91u64);
146161
let a = g * r;
147-
let b = h * r;
148-
let c = DdhTupleNizk::fiat_shamir_challenge(&g, &h, &x_g, &x_h, &a, &b);
162+
let b = (g * S::from(7u64)) * r;
163+
let c = DdhTupleNizk::<G>::challenge(&x_g, &x_h, &a, &b, dst);
149164
assert_eq!(
150165
&c.to_byte_array(),
151-
Hex::decode("30db2f4121471c4af67d2dcfdede1f4aefb15475867c20c0dd5c228c0721f80e")
166+
Hex::decode("0fcba8670c851477df01c27dcd01ba6b780eca4f7c2cb6cd168578430c8fff00")
152167
.unwrap()
153168
.as_slice()
154169
);
170+
// The challenge must depend on the DST.
171+
let other = DdhTupleNizk::<G>::challenge(&x_g, &x_h, &a, &b, b"other");
172+
assert_ne!(c.to_byte_array(), other.to_byte_array());
155173
}
156174

157175
#[test]
158176
fn test_invalid_proofs() {
159177
// x_g/h_g/h=inf should be rejected
178+
let dst = b"test";
160179
let e = S::zero();
161180
let x = S::rand(&mut thread_rng());
162181
let g = G::generator() * S::rand(&mut thread_rng());
163182
let h = g * e;
164183
let x_g = g * x;
165184
let x_h = h * x;
166-
let nizk = DdhTupleNizk::create(&x, &g, &h, &x_g, &x_h, &mut thread_rng());
185+
let nizk = DdhTupleNizk::create(&x, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
167186

168-
assert!(nizk.verify(&G::zero(), &h, &x_g, &x_h).is_err());
169-
assert!(nizk.verify(&g, &G::zero(), &x_g, &x_h).is_err());
170-
assert!(nizk.verify(&g, &h, &G::zero(), &x_h).is_err());
171-
assert!(nizk.verify(&g, &h, &x_g, &G::zero()).is_err());
187+
assert!(nizk.verify(&G::zero(), &h, &x_g, &x_h, dst).is_err());
188+
assert!(nizk.verify(&g, &G::zero(), &x_g, &x_h, dst).is_err());
189+
assert!(nizk.verify(&g, &h, &G::zero(), &x_h, dst).is_err());
190+
assert!(nizk.verify(&g, &h, &x_g, &G::zero(), dst).is_err());
172191
}
173192

174193
#[test]
175194
fn test_serde() {
195+
let dst = b"test";
176196
let e = S::rand(&mut thread_rng());
177197
let x2 = S::rand(&mut thread_rng());
178198
let g = G::generator() * S::rand(&mut thread_rng());
179199
let h = g * e;
180200
let x_g = g * x2;
181201
let x_h = h * x2;
182-
let nizk = DdhTupleNizk::create(&x2, &g, &h, &x_g, &x_h, &mut thread_rng());
183-
assert!(nizk.verify(&g, &h, &x_g, &x_h).is_ok());
202+
let nizk = DdhTupleNizk::create(&x2, &g, &h, &x_g, &x_h, dst, &mut thread_rng());
203+
assert!(nizk.verify(&g, &h, &x_g, &x_h, dst).is_ok());
184204

185205
let as_bytes = bcs::to_bytes(&nizk).unwrap();
186206
let nizk2: DdhTupleNizk<G> = bcs::from_bytes(&as_bytes).unwrap();

0 commit comments

Comments
 (0)