1use alloc::vec::Vec;
5use k256::{ProjectivePoint, Scalar};
6use rand_core::CryptoRng;
7
8use crate::{
9 arith::sbig::{SBig, SRange},
10 curve::{decode_scalar, encode_point, encode_scalar, is_identity, random_nonzero_scalar},
11 error::{Error, Result},
12 params::Params,
13 transcript::{Challenge, Transcript, uint_to_be},
14};
15
16#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct DlogProof {
19 pub e: SBig,
20 pub z: Scalar,
21}
22
23fn sbig_to_scalar(e: &SBig) -> Result<Scalar> {
25 let bytes = uint_to_be(e.mag(), 32)?;
26 let s = decode_scalar(&bytes)?;
27 Ok(if bool::from(e.is_neg()) { -s } else { s })
28}
29
30fn base(tx: &Transcript, x_pub: &ProjectivePoint) -> Transcript {
31 let mut t = tx.clone();
32 t.append("zk", b"dlog");
33 t.append("X", &encode_point(x_pub));
34 t
35}
36
37fn derive_e(t: &Transcript, a: &ProjectivePoint, params: &Params) -> SBig {
38 let mut t = t.clone();
39 t.append("A", &encode_point(a));
40 let mut ch: Challenge = t.challenge("e");
41 ch.pm_pow2(params.ell)
42}
43
44fn e_range(params: &Params) -> SRange {
45 SRange::pm_pow2(params.ell)
46}
47
48impl DlogProof {
49 pub fn prove<R: CryptoRng + ?Sized>(
51 rng: &mut R,
52 tx: &Transcript,
53 params: &Params,
54 x_pub: &ProjectivePoint,
55 x: &Scalar,
56 ) -> Result<Self> {
57 if params.ell == 0 || params.ell > 255 || is_identity(x_pub) {
58 return Err(Error::OutOfRange);
59 }
60 let t = base(tx, x_pub);
61 let alpha = random_nonzero_scalar(rng);
62 let a = ProjectivePoint::GENERATOR * alpha;
63 let e = derive_e(&t, &a, params);
64 let z = alpha + sbig_to_scalar(&e)? * x;
65 Ok(Self { e, z })
66 }
67
68 pub fn verify(&self, tx: &Transcript, params: &Params, x_pub: &ProjectivePoint) -> Result<()> {
70 if params.ell == 0 || params.ell > 255 || is_identity(x_pub) {
71 return Err(Error::VerificationFailed);
72 }
73 if !self.e.in_range(e_range(params)) {
74 return Err(Error::OutOfRange);
75 }
76 let es = sbig_to_scalar(&self.e)?;
77 let a = ProjectivePoint::GENERATOR * self.z - *x_pub * es;
79 if is_identity(&a) {
80 return Err(Error::VerificationFailed);
81 }
82 let t = base(tx, x_pub);
83 if derive_e(&t, &a, params) == self.e {
84 Ok(())
85 } else {
86 Err(Error::VerificationFailed)
87 }
88 }
89
90 pub fn encode(&self, params: &Params) -> Result<Vec<u8>> {
92 let mut out = self.e.to_bytes(e_range(params))?;
93 out.extend_from_slice(&encode_scalar(&self.z));
94 Ok(out)
95 }
96
97 pub fn encoded_len(params: &Params) -> usize {
98 1 + (params.ell.saturating_sub(1) as usize).div_ceil(8).max(1) + 32
99 }
100
101 pub fn decode(bytes: &[u8], params: &Params) -> Result<Self> {
102 if bytes.len() != Self::encoded_len(params) {
103 return Err(Error::InvalidEncoding);
104 }
105 let (eb, zb) = bytes.split_at(bytes.len() - 32);
106 let e = SBig::from_bytes(eb, e_range(params))?;
107 let z = decode_scalar(zb)?;
108 Ok(Self { e, z })
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::params::PARAMS_128;
116 use rand_chacha::ChaCha8Rng;
117 use rand_core::SeedableRng;
118
119 fn setup() -> (ChaCha8Rng, Transcript, ProjectivePoint, Scalar) {
120 let mut rng = ChaCha8Rng::seed_from_u64(1);
121 let x = random_nonzero_scalar(&mut rng);
122 (
123 rng,
124 Transcript::new("test"),
125 ProjectivePoint::GENERATOR * x,
126 x,
127 )
128 }
129
130 #[test]
131 fn honest_and_roundtrip() {
132 let (mut rng, tx, xp, x) = setup();
133 let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
134 p.verify(&tx, &PARAMS_128, &xp).unwrap();
135 let enc = p.encode(&PARAMS_128).unwrap();
136 assert_eq!(enc.len(), 17 + 32);
137 let q = DlogProof::decode(&enc, &PARAMS_128).unwrap();
138 assert_eq!(p, q);
139 q.verify(&tx, &PARAMS_128, &xp).unwrap();
140 assert!(DlogProof::decode(&enc[1..], &PARAMS_128).is_err());
141 let mut longer = enc.clone();
142 longer.push(0);
143 assert!(DlogProof::decode(&longer, &PARAMS_128).is_err());
144 }
145
146 #[test]
147 fn many_seeds() {
148 let (mut rng, tx, xp, x) = setup();
149 for _ in 0..50 {
150 let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
151 p.verify(&tx, &PARAMS_128, &xp).unwrap();
152 }
153 }
154
155 #[test]
156 fn tampering_fails() {
157 let (mut rng, tx, xp, x) = setup();
158 let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
159 let mut b = p.clone();
161 b.z += Scalar::ONE;
162 assert!(b.verify(&tx, &PARAMS_128, &xp).is_err());
163 for d in [1i64, -1] {
165 let mut b = p.clone();
166 b.e = b.e.add(&SBig::from_i64(d));
167 assert!(b.verify(&tx, &PARAMS_128, &xp).is_err());
168 }
169 let mut b = p.clone();
171 b.e = SBig::from_i64(1).add(&SBig::from_uint(
172 crypto_bigint::BoxedUint::one_with_precision(192).shl(127),
173 ));
174 assert_eq!(b.verify(&tx, &PARAMS_128, &xp), Err(Error::OutOfRange));
175 let other = xp + ProjectivePoint::GENERATOR;
177 assert!(p.verify(&tx, &PARAMS_128, &other).is_err());
178 assert!(
179 p.verify(&tx, &PARAMS_128, &ProjectivePoint::IDENTITY)
180 .is_err()
181 );
182 let mut tx2 = Transcript::new("test");
184 tx2.append("x", b"y");
185 assert!(p.verify(&tx2, &PARAMS_128, &xp).is_err());
186 }
187
188 #[test]
189 fn wrong_witness_fails() {
190 let (mut rng, tx, xp, x) = setup();
191 let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &(x + Scalar::ONE)).unwrap();
192 assert!(p.verify(&tx, &PARAMS_128, &xp).is_err());
193 }
194
195 #[test]
196 fn decode_rejects_noncanonical() {
197 let (mut rng, tx, xp, x) = setup();
198 let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
199 let enc = p.encode(&PARAMS_128).unwrap();
200 let mut b = enc.clone();
202 for v in b[17..].iter_mut() {
203 *v = 0xff;
204 }
205 assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
206 let mut b = enc.clone();
208 b[0] = 2;
209 assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
210 let mut b = enc.clone();
212 b[0] = 1;
213 for v in b[1..17].iter_mut() {
214 *v = 0;
215 }
216 assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
217 let mut b = enc.clone();
219 b[0] = 0;
220 b[1] = 0xff;
221 assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
222 }
223}