1use p256::elliptic_curve::Field as _;
30use p256::elliptic_curve::PrimeField;
31use p256::elliptic_curve::sec1::FromSec1Point;
32use p256::elliptic_curve::sec1::ToSec1Point;
33use p256::{AffinePoint, FieldBytes, ProjectivePoint, Scalar};
34use sha2::{Digest, Sha256};
35
36#[derive(Debug, Clone, Copy)]
41pub struct PedersenGens {
42 pub g: AffinePoint,
44 pub h: AffinePoint,
46}
47
48impl PedersenGens {
49 pub fn standard() -> Self {
51 Self {
52 g: ProjectivePoint::GENERATOR.to_affine(),
53 h: hash_to_curve(b"confium-pedersen-h-v1"),
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct PedersenCommitment {
61 pub c: AffinePoint,
63 pub value: Scalar,
65 pub blind: Scalar,
67}
68
69pub fn commit(gens: &PedersenGens, value: Scalar) -> PedersenCommitment {
71 let blind = random_scalar();
72 let c =
73 (ProjectivePoint::from(gens.g) * value + ProjectivePoint::from(gens.h) * blind).to_affine();
74 PedersenCommitment { c, value, blind }
75}
76
77#[derive(Debug, Clone)]
79pub struct PedersenRangeProof {
80 pub bits: u32,
82 pub bit_commitments: Vec<AffinePoint>,
84 pub announcements: Vec<AffinePoint>,
86 pub challenges: Vec<Scalar>,
88 pub responses: Vec<Scalar>,
90}
91
92pub fn prove(
96 gens: &PedersenGens,
97 com: &PedersenCommitment,
98 bits: u32,
99) -> Option<PedersenRangeProof> {
100 let bits = bits.checked_sub(1)?; let bits = bits + 1;
102 if !fits(com.value, bits) {
103 return None;
104 }
105
106 let mut r_is: Vec<Scalar> = (0..bits).map(|_| random_scalar()).collect();
108 let mut weighted: Scalar = Scalar::ZERO;
109 for (i, r) in r_is.iter().enumerate().take(bits as usize) {
110 weighted += pow2(i as u32) * *r;
111 }
112 let delta = com.blind - weighted;
113 let top = (bits - 1) as usize;
116 let inv = invert(pow2(top as u32));
117 r_is[top] += delta * inv;
118
119 let g = ProjectivePoint::from(gens.g);
120 let h = ProjectivePoint::from(gens.h);
121
122 let mut bit_commitments = Vec::with_capacity(bits as usize);
123 let mut announcements = Vec::with_capacity(bits as usize * 2);
124 let mut challenges = Vec::with_capacity(bits as usize);
125 let mut responses = Vec::with_capacity(bits as usize * 2);
126
127 for i in 0..bits {
128 let b = bit_at(com.value, i);
129 let c_i =
130 (g * (if b { Scalar::ONE } else { Scalar::ZERO }) + h * r_is[i as usize]).to_affine();
131 bit_commitments.push(c_i);
132
133 let u = random_scalar();
136 let real_announce = (h * u).to_affine();
137
138 let (e_sim, z_sim) = (random_scalar(), random_scalar());
141 let sim_branch: u32 = if b { 0 } else { 1 };
142 let sim_statement = if sim_branch == 0 {
143 ProjectivePoint::from(c_i)
144 } else {
145 ProjectivePoint::from(c_i) - g
146 };
147 let sim_announce = (h * z_sim - sim_statement * e_sim).to_affine();
148
149 let (a0, a1) = if !b {
153 (real_announce, sim_announce)
154 } else {
155 (sim_announce, real_announce)
156 };
157 let fs = bit_challenge(com.c, i, &c_i, &a0, &a1, bits);
158 let e_real = fs - e_sim;
159 let z_real = u + e_real * r_is[i as usize];
160
161 announcements.push(a0);
162 announcements.push(a1);
163 if !b {
164 challenges.push(e_real);
165 responses.push(z_real);
166 responses.push(z_sim);
167 } else {
168 challenges.push(e_sim);
169 responses.push(z_sim);
170 responses.push(z_real);
171 }
172 }
173
174 Some(PedersenRangeProof {
175 bits,
176 bit_commitments,
177 announcements,
178 challenges,
179 responses,
180 })
181}
182
183pub fn verify(gens: &PedersenGens, c: &AffinePoint, proof: &PedersenRangeProof) -> bool {
189 let n = proof.bits as usize;
190 if proof.bit_commitments.len() != n
191 || proof.announcements.len() != n * 2
192 || proof.challenges.len() != n
193 || proof.responses.len() != n * 2
194 {
195 return false;
196 }
197
198 let g = ProjectivePoint::from(gens.g);
200 let h = ProjectivePoint::from(gens.h);
201 let mut aggregate = ProjectivePoint::IDENTITY;
202 for (i, c_i) in proof.bit_commitments.iter().enumerate() {
203 aggregate += ProjectivePoint::from(*c_i) * pow2(i as u32);
204 }
205 if aggregate.to_affine() != *c {
206 return false;
207 }
208
209 for i in 0..n {
211 let c_i = proof.bit_commitments[i];
212 let a0 = proof.announcements[i * 2];
213 let a1 = proof.announcements[i * 2 + 1];
214 let e0 = proof.challenges[i];
215 let z0 = proof.responses[i * 2];
216 let z1 = proof.responses[i * 2 + 1];
217
218 let fs = bit_challenge(*c, i as u32, &c_i, &a0, &a1, proof.bits);
219 let e1 = fs - e0;
220
221 let lhs0 = h * z0;
223 let rhs0 = ProjectivePoint::from(a0) + ProjectivePoint::from(c_i) * e0;
224 if lhs0 != rhs0 {
225 return false;
226 }
227 let lhs1 = h * z1;
229 let rhs1 = ProjectivePoint::from(a1) + (ProjectivePoint::from(c_i) - g) * e1;
230 if lhs1 != rhs1 {
231 return false;
232 }
233 }
234 true
235}
236
237fn bit_challenge(
240 c: AffinePoint,
241 i: u32,
242 c_i: &AffinePoint,
243 a0: &AffinePoint,
244 a1: &AffinePoint,
245 bits: u32,
246) -> Scalar {
247 let mut hasher = Sha256::new();
248 hasher.update(b"confium-range-v1");
249 hasher.update(bits.to_be_bytes());
250 hasher.update(c.to_sec1_point(true).as_bytes());
251 hasher.update(i.to_be_bytes());
252 hasher.update(c_i.to_sec1_point(true).as_bytes());
253 hasher.update(a0.to_sec1_point(true).as_bytes());
254 hasher.update(a1.to_sec1_point(true).as_bytes());
255 let bytes: [u8; 32] = hasher.finalize().into();
256 let mut bytes = bytes;
258 loop {
259 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
260 return s;
261 }
262 let mut h = Sha256::new();
263 h.update(b"confium-scalar-reduce-v1");
264 h.update(bytes);
265 bytes = h.finalize().into();
266 }
267}
268
269fn fits(v: Scalar, bits: u32) -> bool {
270 let repr = v.to_repr();
273 repr.as_slice()[..(256 - bits) as usize / 8]
274 .iter()
275 .all(|&b| b == 0)
276 && (((256 - bits) % 8) == 0
277 || repr.as_slice()[(256 - bits) as usize / 8] < (1u8 << ((256 - bits) % 8)))
278}
279
280fn bit_at(v: Scalar, i: u32) -> bool {
281 let repr = v.to_repr();
282 let byte = repr.as_slice()[31 - (i / 8) as usize];
283 (byte >> (i % 8)) & 1 == 1
284}
285
286fn pow2(i: u32) -> Scalar {
287 let two = Scalar::from(2u64);
289 let mut acc = Scalar::ONE;
290 let mut base = two;
291 let mut e = i;
292 while e > 0 {
293 if e & 1 == 1 {
294 acc *= base;
295 }
296 base = base * base;
297 e >>= 1;
298 }
299 acc
300}
301
302fn random_scalar() -> Scalar {
303 use getrandom::SysRng;
304 use p256::elliptic_curve::rand_core::UnwrapErr;
305 Scalar::random(&mut UnwrapErr(SysRng))
306}
307
308fn invert(s: Scalar) -> Scalar {
309 Option::<Scalar>::from(s.invert()).unwrap_or(Scalar::ONE)
310}
311
312fn hash_to_curve(label: &[u8]) -> AffinePoint {
314 let mut counter: u32 = 0;
315 loop {
316 let mut hasher = Sha256::new();
317 hasher.update(b"confium-hash-to-curve-v1");
318 hasher.update(label);
319 hasher.update(counter.to_be_bytes());
320 let bytes: [u8; 32] = hasher.finalize().into();
321 let mut sec1 = [0u8; 33];
323 sec1[0] = 0x02 | (bytes[31] & 1);
324 sec1[1..33].copy_from_slice(&bytes);
325 let enc = p256::elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(&sec1);
326 if let Ok(enc) = enc {
327 let p_opt = Option::<AffinePoint>::from(AffinePoint::from_sec1_point(&enc));
328 if let Some(p) = p_opt {
329 if p != AffinePoint::IDENTITY && p != ProjectivePoint::GENERATOR.to_affine() {
331 return p;
332 }
333 }
334 }
335 counter += 1;
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 fn gens() -> PedersenGens {
344 PedersenGens::standard()
345 }
346
347 fn scalar(v: u64) -> Scalar {
348 Scalar::from(v)
349 }
350
351 #[test]
352 fn round_trip_zero() {
353 let gens = gens();
354 let com = commit(&gens, scalar(0));
355 let proof = prove(&gens, &com, 64).unwrap();
356 assert!(verify(&gens, &com.c, &proof));
357 }
358
359 #[test]
360 fn round_trip_max() {
361 let gens = gens();
362 let com = commit(&gens, scalar(u64::MAX));
363 let proof = prove(&gens, &com, 64).unwrap();
364 assert!(verify(&gens, &com.c, &proof));
365 }
366
367 #[test]
368 fn round_trip_mid() {
369 let gens = gens();
370 let com = commit(&gens, scalar(4242));
371 let proof = prove(&gens, &com, 64).unwrap();
372 assert!(verify(&gens, &com.c, &proof));
373 }
374
375 #[test]
376 fn round_trip_top_bit() {
377 let gens = gens();
378 let com = commit(&gens, scalar(1 << 63));
379 let proof = prove(&gens, &com, 64).unwrap();
380 assert!(verify(&gens, &com.c, &proof));
381 }
382
383 #[test]
384 fn rejects_proof_for_a_different_commitment() {
385 let gens = gens();
386 let com = commit(&gens, scalar(42));
387 let other = commit(&gens, scalar(43));
388 let proof = prove(&gens, &com, 64).unwrap();
389 assert!(!verify(&gens, &other.c, &proof));
391 }
392
393 #[test]
394 fn rejects_tampered_bit_commitment() {
395 let gens = gens();
396 let com = commit(&gens, scalar(300));
397 let mut proof = prove(&gens, &com, 64).unwrap();
398 proof.bit_commitments[0] = (ProjectivePoint::from(proof.bit_commitments[0])
401 + ProjectivePoint::from(gens.g))
402 .to_affine();
403 assert!(!verify(&gens, &com.c, &proof));
404 }
405
406 #[test]
407 fn rejects_tampered_response() {
408 let gens = gens();
409 let com = commit(&gens, scalar(7));
410 let mut proof = prove(&gens, &com, 64).unwrap();
411 proof.responses[0] += Scalar::ONE;
412 assert!(!verify(&gens, &com.c, &proof));
413 }
414
415 #[test]
416 fn rejects_tampered_announcement() {
417 let gens = gens();
418 let com = commit(&gens, scalar(7));
419 let mut proof = prove(&gens, &com, 64).unwrap();
420 proof.announcements[1] = (ProjectivePoint::from(proof.announcements[1])
423 + ProjectivePoint::from(gens.h))
424 .to_affine();
425 assert!(!verify(&gens, &com.c, &proof));
426 }
427
428 #[test]
429 fn rejects_cross_bit_proof_splice() {
430 let gens = gens();
431 let com = commit(&gens, scalar(0b1011));
432 let mut proof = prove(&gens, &com, 64).unwrap();
433 proof.bit_commitments.swap(0, 1);
436 proof.announcements.swap(0, 2);
437 proof.announcements.swap(1, 3);
438 proof.challenges.swap(0, 1);
439 proof.responses.swap(0, 2);
440 proof.responses.swap(1, 3);
441 assert!(!verify(&gens, &com.c, &proof));
442 }
443
444 #[test]
445 fn rejects_out_of_range_value_at_prove_time() {
446 let gens = gens();
447 let com = PedersenCommitment {
449 c: (ProjectivePoint::from(gens.g) * pow2(64)
450 + ProjectivePoint::from(gens.h) * scalar(5))
451 .to_affine(),
452 value: pow2(64),
453 blind: scalar(5),
454 };
455 assert!(prove(&gens, &com, 64).is_none());
456 }
457
458 #[test]
459 fn rejects_malformed_shape() {
460 let gens = gens();
461 let com = commit(&gens, scalar(1));
462 let mut proof = prove(&gens, &com, 64).unwrap();
463 proof.responses.pop();
464 assert!(!verify(&gens, &com.c, &proof));
465 }
466
467 #[test]
468 fn second_generator_is_not_the_identity_or_g() {
469 let gens = gens();
470 assert_ne!(gens.h, AffinePoint::IDENTITY);
471 assert_ne!(gens.h, gens.g);
472 }
473
474 #[test]
475 fn commitment_hides_equal_values() {
476 let gens = gens();
477 let a = commit(&gens, scalar(9));
478 let b = commit(&gens, scalar(9));
479 assert_ne!(a.c, b.c);
481 }
482}