1use alloc::vec::Vec;
8use crypto_bigint::BoxedUint;
9use k256::{ProjectivePoint, Scalar};
10use sha2::{Digest, Sha256};
11use zeroize::{ZeroizeOnDrop, Zeroizing};
12
13use crate::{
14 arith::monty::Modulus,
15 curve::{decode_point, decode_scalar, encode_point, encode_scalar},
16 error::{Error, Result},
17 params::Params,
18 ssid::Ssid,
19 transcript::{Transcript, uint_from_be, uint_to_be},
20 zk::df::DfStatement,
21};
22
23pub mod client;
24pub mod server;
25
26pub use client::{ClientKeygen0, ClientKeygen1};
27pub use server::{ServerKeygen0, ServerKeygen1};
28
29pub const KEYGEN_TIMEOUT_MS: u64 = 60_000;
31
32pub trait Clock {
34 fn now_ms(&self) -> u64;
35}
36
37pub(crate) fn tx_base(params: &Params, ssid: &Ssid, role: &str) -> Transcript {
39 let mut t = Transcript::new("keygen");
40 t.append("params_id", &[params.id as u8]);
41 t.append("ssid", &ssid.encode());
42 t.append("role", role.as_bytes());
43 t
44}
45
46pub(crate) fn commitment(params: &Params, ssid: &Ssid, x2_pub: &ProjectivePoint) -> [u8; 32] {
48 let mut t = Transcript::new("ro/kg-commit");
49 t.append("params_id", &[params.id as u8]);
50 t.append("ssid", &ssid.encode());
51 t.append("S", ssid.server_id());
52 t.append("X2", &encode_point(x2_pub));
53 t.challenge("B").seed()
54}
55
56pub(crate) fn canonical_uint(bytes: &[u8], len: usize, bound: &BoxedUint) -> Result<BoxedUint> {
58 if bytes.len() != len {
59 return Err(Error::InvalidEncoding);
60 }
61 let x = uint_from_be(bytes, bound.bits_precision())?;
62 let b = crate::arith::sbig::resized(bound, x.bits_precision());
63 if x.cmp_vartime(&b) != core::cmp::Ordering::Less {
64 return Err(Error::OutOfRange);
65 }
66 Ok(x)
67}
68
69pub(crate) fn decode_client_tuple(
71 params: &Params,
72 m_hat: &[u8],
73 v: &[u8],
74 u1: &[u8],
75 u2: &[u8],
76) -> Result<DfStatement> {
77 let len = params.n_client as usize / 8;
78 if m_hat.len() != len {
79 return Err(Error::InvalidEncoding);
80 }
81 let m = uint_from_be(m_hat, params.n_client)?;
82 if m.bits_vartime() != params.n_client {
83 return Err(Error::OutOfRange);
84 }
85 Modulus::new(m.clone())?;
86 let vv = canonical_uint(v, len, &m)?;
87 let a = canonical_uint(u1, len, &m)?;
88 let b = canonical_uint(u2, len, &m)?;
89 DfStatement::client(&m, &vv, &a, &b)
90}
91
92pub(crate) fn encode_client_tuple(params: &Params, st: &DfStatement) -> Result<[Vec<u8>; 4]> {
93 let len = params.n_client as usize / 8;
94 let enc = |r: &crate::arith::monty::Residue| uint_to_be(&r.retrieve(), len);
95 Ok([
96 uint_to_be(st.n_hat.modulus().as_ref(), len)?,
97 enc(&st.t)?,
98 enc(&st.s1)?,
99 enc(&st.s2)?,
100 ])
101}
102
103fn key_id(x_pub: &ProjectivePoint, e: &[u8]) -> [u8; 32] {
104 let mut h = Sha256::new();
105 h.update(encode_point(x_pub));
106 h.update(e);
107 h.finalize().into()
108}
109
110const CHAIN_CODE_DOMAIN: &[u8] = b"tecdsa-1ole/hd-chain-code/v1";
112
113fn chain_code(x_pub: &ProjectivePoint, e: &[u8]) -> [u8; 32] {
117 let mut h = Sha256::new();
118 h.update(CHAIN_CODE_DOMAIN);
119 h.update(encode_point(x_pub));
120 h.update(e);
121 h.finalize().into()
122}
123
124const SHARE_VERSION: u8 = 0x01;
125
126macro_rules! key_share {
127 ($name:ident, $field:ident, $doc:literal) => {
128 #[doc = $doc]
129 #[derive(ZeroizeOnDrop)]
130 pub struct $name {
131 $field: Scalar,
132 #[zeroize(skip)]
133 x_pub: ProjectivePoint,
134 #[zeroize(skip)]
136 e: Vec<u8>,
137 }
138
139 impl core::fmt::Debug for $name {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 f.write_str(concat!(stringify!($name), "(***)"))
142 }
143 }
144
145 impl $name {
146 pub(crate) fn new($field: Scalar, x_pub: ProjectivePoint, e: Vec<u8>) -> Self {
147 Self { $field, x_pub, e }
148 }
149 pub fn public_key(&self) -> &ProjectivePoint {
151 &self.x_pub
152 }
153 pub fn e_bytes(&self) -> &[u8] {
155 &self.e
156 }
157 pub fn chain_code(&self) -> [u8; 32] {
159 chain_code(&self.x_pub, &self.e)
160 }
161 pub fn key_id(&self) -> [u8; 32] {
163 key_id(&self.x_pub, &self.e)
164 }
165 #[allow(dead_code)]
166 pub(crate) fn secret(&self) -> &Scalar {
167 &self.$field
168 }
169 pub fn export_secret(&self) -> Zeroizing<Vec<u8>> {
171 let mut out = Zeroizing::new(Vec::with_capacity(1 + 32 + 33 + self.e.len()));
172 out.push(SHARE_VERSION);
173 out.extend_from_slice(&encode_scalar(&self.$field));
174 out.extend_from_slice(&encode_point(&self.x_pub));
175 out.extend_from_slice(&self.e);
176 out
177 }
178 pub fn import_secret(bytes: &[u8], params: &Params) -> Result<Self> {
179 let elen = (2 * params.n_rsa as usize).div_ceil(8);
180 if bytes.len() != 1 + 32 + 33 + elen || bytes[0] != SHARE_VERSION {
181 return Err(Error::InvalidEncoding);
182 }
183 Ok(Self {
184 $field: decode_scalar(&bytes[1..33])?,
185 x_pub: decode_point(&bytes[33..66])?,
186 e: bytes[66..].to_vec(),
187 })
188 }
189 }
190 };
191}
192
193key_share!(
194 ClientKeyShare,
195 x1,
196 "Client output of keygen: `x₁`, `X`, `E`."
197);
198key_share!(
199 ServerKeyShare,
200 x2,
201 "Server output of keygen: `x₂ = x₂′ mod q`, `X`, `E`."
202);
203
204#[cfg(test)]
205mod tests {
206 extern crate std;
207 use super::*;
208 use crate::{
209 arith::sbig::SBig,
210 params::PARAMS_128,
211 testutil::{TestClock, fx, params},
212 };
213 use core::cell::Cell;
214 use k256::ProjectivePoint;
215 use rand_chacha::ChaCha8Rng;
216 use rand_core::SeedableRng;
217
218 fn run(seed: u64) -> (ClientKeyShare, ServerKeyShare) {
219 let p = params();
220 let mut rng = ChaCha8Rng::seed_from_u64(seed);
221 let clock = TestClock(Cell::new(1_000));
222 let (s1, m1) = ServerKeygen0::start(&mut rng, &p, &fx().data, b"client-1").unwrap();
223 let (c1, m2) =
224 ClientKeygen0::on_msg1(&mut rng, &clock, &fx().setup, b"client-1", &m1).unwrap();
225 let (server_share, m3) = s1.on_msg2(&mut rng, &m2).unwrap();
226 let client_share = c1.on_msg3(&clock, &m3).unwrap();
227 (client_share, server_share)
228 }
229
230 #[test]
231 fn chain_code_is_shared_and_key_bound() {
232 let (c, s) = run(5);
233 assert_eq!(c.chain_code(), s.chain_code());
234 let (c2, _) = run(6);
235 assert_ne!(c.chain_code(), c2.chain_code());
236 assert_ne!(c.chain_code(), c.key_id());
237 }
238
239 #[test]
240 fn honest_keygen() {
241 let (c, s) = run(5);
242 assert_eq!(c.public_key(), s.public_key());
243 assert_eq!(c.e_bytes(), s.e_bytes());
244 assert_eq!(c.key_id(), s.key_id());
245 let sum = *c.secret() + *s.secret();
246 assert_eq!(ProjectivePoint::GENERATOR * sum, *c.public_key());
247 let pk = fx().data.paillier_pk().unwrap();
249 let e = pk
250 .check_ciphertext(&uint_from_be(s.e_bytes(), 1024).unwrap())
251 .unwrap();
252 let m = fx().sk.paillier_sk().dec(&e).unwrap();
253 let n = pk.n().clone();
255 let half = n.shr_vartime(1).unwrap();
256 let signed = if m.cmp_vartime(&half) == core::cmp::Ordering::Greater {
257 SBig::from_uint(crate::arith::sbig::resized(&m, n.bits_precision()))
258 .sub(&SBig::from_uint(n))
259 } else {
260 SBig::from_uint(m)
261 };
262 assert_eq!(crate::curve::sbig_to_scalar(&signed).unwrap(), *s.secret());
263 assert_eq!(std::format!("{c:?}"), "ClientKeyShare(***)");
264 let back = ServerKeyShare::import_secret(&s.export_secret(), ¶ms()).unwrap();
266 assert_eq!(back.key_id(), s.key_id());
267 assert_eq!(*back.secret(), *s.secret());
268 assert!(ClientKeyShare::import_secret(&c.export_secret()[1..], ¶ms()).is_err());
269 }
270
271 #[test]
272 fn two_sessions_differ() {
273 let (a, _) = run(5);
274 let (b, _) = run(6);
275 assert_ne!(a.public_key(), b.public_key());
276 }
277
278 #[test]
279 fn tampered_messages_are_rejected() {
280 let p = params();
281 let clock = TestClock(Cell::new(0));
282 let mut rng = ChaCha8Rng::seed_from_u64(9);
283 let (s1, m1) = ServerKeygen0::start(&mut rng, &p, &fx().data, b"client-1").unwrap();
284 let (c1, m2) =
285 ClientKeygen0::on_msg1(&mut rng, &clock, &fx().setup, b"client-1", &m1).unwrap();
286
287 let positions = [
289 4,
290 20,
291 60,
292 100,
293 200,
294 300,
295 m2.len() / 2,
296 m2.len() - 40,
297 m2.len() - 1,
298 ];
299 for &i in positions.iter().filter(|i| **i < m2.len()) {
300 let mut bad = m2.clone();
301 bad[i] ^= 0x01;
302 let s = clone_server_state(&s1);
303 assert!(s.on_msg2(&mut rng, &bad).is_err(), "msg2 flip at {i}");
304 }
305 assert!(
307 clone_server_state(&s1)
308 .on_msg2(&mut rng, &m2[..m2.len() - 1])
309 .is_err()
310 );
311 let mut longer = m2.clone();
312 longer.push(0);
313 assert!(clone_server_state(&s1).on_msg2(&mut rng, &longer).is_err());
314 let mut wrong_type = m2.clone();
315 wrong_type[3] = 3;
316 assert!(
317 clone_server_state(&s1)
318 .on_msg2(&mut rng, &wrong_type)
319 .is_err()
320 );
321
322 let (_, m3) = s1.on_msg2(&mut rng, &m2).unwrap();
323 let positions = [
325 4,
326 10,
327 40,
328 100,
329 200,
330 300,
331 m3.len() / 2,
332 m3.len() - 100,
333 m3.len() - 1,
334 ];
335 for &i in positions.iter().filter(|i| **i < m3.len()) {
336 let mut bad = m3.clone();
337 bad[i] ^= 0x01;
338 assert!(
339 clone_client_state(&c1).on_msg3(&clock, &bad).is_err(),
340 "msg3 flip at {i}"
341 );
342 }
343 assert!(c1.on_msg3(&clock, &m3).is_ok());
344 }
345
346 #[test]
347 fn timer_and_session_binding() {
348 let p = params();
349 let mut rng = ChaCha8Rng::seed_from_u64(10);
350 let clock = TestClock(Cell::new(0));
351 let (s1, m1) = ServerKeygen0::start(&mut rng, &p, &fx().data, b"client-1").unwrap();
352 let (c1, m2) =
353 ClientKeygen0::on_msg1(&mut rng, &clock, &fx().setup, b"client-1", &m1).unwrap();
354 let (_, m3) = s1.on_msg2(&mut rng, &m2).unwrap();
355
356 clock.0.set(KEYGEN_TIMEOUT_MS);
358 assert!(clone_client_state(&c1).on_msg3(&clock, &m3).is_ok());
359 clock.0.set(KEYGEN_TIMEOUT_MS + 1);
360 assert_eq!(
361 clone_client_state(&c1).on_msg3(&clock, &m3).unwrap_err(),
362 Error::Timeout
363 );
364
365 clock.0.set(0);
367 let (s2, m1b) = ServerKeygen0::start(&mut rng, &p, &fx().data, b"client-1").unwrap();
368 let (c2, m2b) =
369 ClientKeygen0::on_msg1(&mut rng, &clock, &fx().setup, b"client-OTHER", &m1b).unwrap();
370 assert!(s2.on_msg2(&mut rng, &m2b).is_err());
371 drop(c2);
372
373 let (s3, m1c) = ServerKeygen0::start(&mut rng, &p, &fx().data, b"client-1").unwrap();
375 let (c3, m2c) =
376 ClientKeygen0::on_msg1(&mut rng, &clock, &fx().setup, b"client-1", &m1c).unwrap();
377 let (_, m3c) = s3.on_msg2(&mut rng, &m2c).unwrap();
378 assert!(c3.on_msg3(&clock, &m3).is_err());
379 let _ = m3c;
380 }
381
382 fn clone_server_state(s: &ServerKeygen1) -> ServerKeygen1 {
384 s.test_clone()
385 }
386 fn clone_client_state(s: &ClientKeygen1) -> ClientKeygen1 {
387 s.test_clone()
388 }
389
390 #[test]
391 #[ignore = "full-size: run with --release --ignored --nocapture"]
392 fn full_size_keygen() {
393 use crate::setup::server_setup;
394 use std::time::Instant;
395 let p = PARAMS_128;
396 let mut rng = ChaCha8Rng::seed_from_u64(77);
397 let (data, _sk) = server_setup(&mut rng, &p, [1u8; 32], b"server").unwrap();
398 let setup = data.verify(&p).unwrap();
399 let clock = TestClock(Cell::new(0));
400 let t = Instant::now();
401 let (s1, m1) = ServerKeygen0::start(&mut rng, &p, &data, b"client").unwrap();
402 let t_s1 = t.elapsed();
403 let t = Instant::now();
404 let (c1, m2) = ClientKeygen0::on_msg1(&mut rng, &clock, &setup, b"client", &m1).unwrap();
405 let t_c = t.elapsed();
406 let t = Instant::now();
407 let (ss, m3) = s1.on_msg2(&mut rng, &m2).unwrap();
408 let t_s2 = t.elapsed();
409 let t = Instant::now();
410 let cs = c1.on_msg3(&clock, &m3).unwrap();
411 let t_c2 = t.elapsed();
412 assert_eq!(cs.public_key(), ss.public_key());
413 std::eprintln!(
414 "keygen: S1 {t_s1:?} ({} B) | C step 2 {t_c:?} ({} B) | S step 3 {t_s2:?} ({} B) | C step 4 {t_c2:?}",
415 m1.len(),
416 m2.len(),
417 m3.len()
418 );
419 }
420}