confium_crypto_vss/
nizk.rs1use getrandom::SysRng;
4use p256::elliptic_curve::PrimeField;
5use p256::elliptic_curve::rand_core::{Rng, UnwrapErr};
6use p256::elliptic_curve::sec1::ToSec1Point;
7use p256::{AffinePoint, FieldBytes, ProjectivePoint, Scalar};
8use sha2::{Digest, Sha256};
9
10fn hash_to_scalar(mut fb: [u8; 32]) -> Scalar {
15 loop {
16 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(fb))) {
17 return s;
18 }
19 let mut h = Sha256::new();
20 h.update(b"confium-scalar-reduce-v1");
21 h.update(fb);
22 fb = h.finalize().into();
23 }
24}
25
26fn random_nonce() -> Scalar {
28 loop {
29 let mut b = [0u8; 32];
30 UnwrapErr(SysRng).fill_bytes(&mut b);
31 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(b))) {
32 return s;
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct NizkProof {
40 pub commitment: AffinePoint,
41 pub challenge: Scalar,
42 pub response: Scalar,
43}
44
45pub fn prove_dlog(secret: &Scalar) -> NizkProof {
47 let nonce = random_nonce();
48
49 let commitment = (ProjectivePoint::GENERATOR * nonce).to_affine();
50 let public = (ProjectivePoint::GENERATOR * secret).to_affine();
51
52 let challenge = fiat_shamir_challenge(&public, &commitment, b"dlog");
53 let response = nonce + challenge * secret;
54
55 NizkProof {
56 commitment,
57 challenge,
58 response,
59 }
60}
61
62pub fn verify_dlog(public: &AffinePoint, proof: &NizkProof) -> bool {
64 let expected_challenge = fiat_shamir_challenge(public, &proof.commitment, b"dlog");
65 if expected_challenge != proof.challenge {
66 return false;
67 }
68 let lhs = ProjectivePoint::GENERATOR * proof.response;
70 let rhs =
71 ProjectivePoint::from(proof.commitment) + ProjectivePoint::from(*public) * proof.challenge;
72 lhs == rhs
73}
74
75pub fn prove_dlog_equality(
77 secret: &Scalar,
78 g1: &AffinePoint,
79 g2: &AffinePoint,
80) -> (NizkProof, NizkProof) {
81 let nonce = random_nonce();
82
83 let commit1 = (ProjectivePoint::from(*g1) * nonce).to_affine();
84 let commit2 = (ProjectivePoint::from(*g2) * nonce).to_affine();
85
86 let y1 = (ProjectivePoint::from(*g1) * secret).to_affine();
87 let y2 = (ProjectivePoint::from(*g2) * secret).to_affine();
88
89 let challenge = equality_challenge(&y1, &y2, &commit1, &commit2);
90 let response = nonce + challenge * secret;
91
92 (
93 NizkProof {
94 commitment: commit1,
95 challenge,
96 response,
97 },
98 NizkProof {
99 commitment: commit2,
100 challenge,
101 response,
102 },
103 )
104}
105
106pub fn verify_dlog_equality(
108 y1: &AffinePoint,
109 y2: &AffinePoint,
110 g1: &AffinePoint,
111 g2: &AffinePoint,
112 proof1: &NizkProof,
113 proof2: &NizkProof,
114) -> bool {
115 if proof1.challenge != proof2.challenge || proof1.response != proof2.response {
117 return false;
118 }
119 let expected_challenge = equality_challenge(y1, y2, &proof1.commitment, &proof2.commitment);
120 if expected_challenge != proof1.challenge {
121 return false;
122 }
123 let lhs1 = ProjectivePoint::from(*g1) * proof1.response;
125 let rhs1 =
126 ProjectivePoint::from(proof1.commitment) + ProjectivePoint::from(*y1) * proof1.challenge;
127 let lhs2 = ProjectivePoint::from(*g2) * proof2.response;
128 let rhs2 =
129 ProjectivePoint::from(proof2.commitment) + ProjectivePoint::from(*y2) * proof2.challenge;
130 lhs1 == rhs1 && lhs2 == rhs2
131}
132
133fn fiat_shamir_challenge(public: &AffinePoint, commitment: &AffinePoint, domain: &[u8]) -> Scalar {
134 let mut hasher = Sha256::new();
135 hasher.update(b"nizk");
136 hasher.update(domain);
137 hasher.update(public.to_sec1_point(true).as_bytes());
138 hasher.update(commitment.to_sec1_point(true).as_bytes());
139 let fb: [u8; 32] = hasher.finalize().into();
140 hash_to_scalar(fb)
141}
142
143fn equality_challenge(
144 y1: &AffinePoint,
145 y2: &AffinePoint,
146 c1: &AffinePoint,
147 c2: &AffinePoint,
148) -> Scalar {
149 let mut hasher = Sha256::new();
150 hasher.update(b"nizk-equality");
151 hasher.update(y1.to_sec1_point(true).as_bytes());
152 hasher.update(y2.to_sec1_point(true).as_bytes());
153 hasher.update(c1.to_sec1_point(true).as_bytes());
154 hasher.update(c2.to_sec1_point(true).as_bytes());
155 let fb: [u8; 32] = hasher.finalize().into();
156 hash_to_scalar(fb)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use p256::elliptic_curve::Field;
163
164 #[test]
165 fn dlog_proof_verifies() {
166 let secret = Scalar::random(&mut UnwrapErr(SysRng));
167 let proof = prove_dlog(&secret);
168 let public = (ProjectivePoint::GENERATOR * secret).to_affine();
169 assert!(verify_dlog(&public, &proof));
170 }
171
172 #[test]
173 fn dlog_wrong_public_rejected() {
174 let secret = Scalar::random(&mut UnwrapErr(SysRng));
175 let proof = prove_dlog(&secret);
176 let wrong =
177 (ProjectivePoint::GENERATOR * Scalar::random(&mut UnwrapErr(SysRng))).to_affine();
178 assert!(!verify_dlog(&wrong, &proof));
179 }
180
181 #[test]
182 fn dlog_tampered_response_rejected() {
183 let secret = Scalar::random(&mut UnwrapErr(SysRng));
184 let mut proof = prove_dlog(&secret);
185 let public = (ProjectivePoint::GENERATOR * secret).to_affine();
186 proof.response += Scalar::ONE;
187 assert!(!verify_dlog(&public, &proof));
188 }
189
190 #[test]
191 fn dlog_proof_non_deterministic() {
192 let secret = Scalar::random(&mut UnwrapErr(SysRng));
193 let p1 = prove_dlog(&secret);
194 let p2 = prove_dlog(&secret);
195 assert_ne!(p1.commitment, p2.commitment);
196 }
197
198 #[test]
199 fn equality_proof_verifies() {
200 let secret = Scalar::random(&mut UnwrapErr(SysRng));
201 let g1 = AffinePoint::GENERATOR;
202 let g2 = (ProjectivePoint::GENERATOR * Scalar::from(2u32)).to_affine();
203
204 let (p1, p2) = prove_dlog_equality(&secret, &g1, &g2);
205 let y1 = (ProjectivePoint::from(g1) * secret).to_affine();
206 let y2 = (ProjectivePoint::from(g2) * secret).to_affine();
207
208 assert!(verify_dlog_equality(&y1, &y2, &g1, &g2, &p1, &p2));
209 }
210
211 #[test]
212 fn equality_wrong_secret_rejected() {
213 let secret = Scalar::random(&mut UnwrapErr(SysRng));
214 let g1 = AffinePoint::GENERATOR;
215 let g2 = (ProjectivePoint::GENERATOR * Scalar::from(2u32)).to_affine();
216
217 let (p1, p2) = prove_dlog_equality(&secret, &g1, &g2);
218 let wrong = Scalar::random(&mut UnwrapErr(SysRng));
219 let y1 = (ProjectivePoint::from(g1) * wrong).to_affine();
220 let y2 = (ProjectivePoint::from(g2) * secret).to_affine();
221
222 assert!(!verify_dlog_equality(&y1, &y2, &g1, &g2, &p1, &p2));
223 }
224
225 #[test]
226 fn nizk_proof_serializes() {
227 let secret = Scalar::random(&mut UnwrapErr(SysRng));
228 let proof = prove_dlog(&secret);
229 assert!(proof.challenge != Scalar::ZERO || proof.response != Scalar::ZERO);
231 }
232}
233
234#[cfg(test)]
235mod adversarial_tests {
236 use super::*;
240 use p256::elliptic_curve::Field as _;
241
242 fn keypair() -> (Scalar, AffinePoint) {
243 let sk = Scalar::random(&mut UnwrapErr(SysRng));
244 let pk = (ProjectivePoint::GENERATOR * sk).to_affine();
245 (sk, pk)
246 }
247
248 #[test]
249 fn rejects_forged_challenge_dlog() {
250 let (sk, pk) = keypair();
251 let proof = prove_dlog(&sk);
252 let mut forged = proof.clone();
253 forged.challenge += Scalar::ONE; assert!(!verify_dlog(&pk, &forged));
255 }
256
257 #[test]
258 fn rejects_proof_for_a_different_key() {
259 let (sk, _) = keypair();
260 let (_, other_pk) = keypair();
261 let proof = prove_dlog(&sk);
262 assert!(!verify_dlog(&other_pk, &proof));
264 }
265
266 #[test]
267 fn rejects_tampered_response_dlog() {
268 let (sk, pk) = keypair();
269 let mut proof = prove_dlog(&sk);
270 proof.response += Scalar::ONE;
271 assert!(!verify_dlog(&pk, &proof));
272 }
273
274 #[test]
275 fn rejects_mismatched_equality_proofs() {
276 let (sk, _) = keypair();
277 let g1 = ProjectivePoint::GENERATOR.to_affine();
278 let g2 = (ProjectivePoint::GENERATOR * Scalar::from(7u64)).to_affine();
279 let (p1, mut p2) = prove_dlog_equality(&sk, &g1, &g2);
280 p2.response += Scalar::ONE;
282 let y1 = (ProjectivePoint::from(g1) * sk).to_affine();
283 let y2 = (ProjectivePoint::from(g2) * sk).to_affine();
284 assert!(!verify_dlog_equality(&y1, &y2, &g1, &g2, &p1, &p2));
285 }
286}