1use k256::{
8 ProjectivePoint, Scalar, U256,
9 ecdsa::{
10 RecoveryId, Signature as EcdsaSignature, VerifyingKey, signature::hazmat::PrehashVerifier,
11 },
12 elliptic_curve::{ops::Reduce, point::AffineCoordinates, scalar::IsHigh},
13};
14use rand_core::CryptoRng;
15
16use crate::{
17 curve::{decode_scalar, encode_point, encode_scalar},
18 error::{Error, Result},
19 hd::HdPath,
20 params::Params,
21 ssid::Ssid,
22 transcript::Transcript,
23};
24
25pub mod client;
26pub mod server;
27
28pub use client::ClientSign;
29pub use server::{ServerSign0, ServerSign1};
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct Signature {
34 pub r: Scalar,
35 pub s: Scalar,
36 pub recid: u8,
38}
39
40impl Signature {
41 pub fn to_bytes(&self) -> [u8; 64] {
43 let mut out = [0u8; 64];
44 out[..32].copy_from_slice(&encode_scalar(&self.r));
45 out[32..].copy_from_slice(&encode_scalar(&self.s));
46 out
47 }
48
49 pub fn to_ecdsa(&self) -> Result<EcdsaSignature> {
50 EcdsaSignature::from_scalars(self.r, self.s).map_err(|_| Error::InvalidEncoding)
51 }
52
53 pub fn recovery_id(&self) -> Result<RecoveryId> {
54 RecoveryId::from_byte(self.recid).ok_or(Error::InvalidEncoding)
55 }
56}
57
58pub(crate) fn digest_scalar(digest: &[u8; 32]) -> Scalar {
60 <Scalar as Reduce<U256>>::reduce(&U256::from_be_slice(digest))
61}
62
63pub(crate) fn check_digest(digest: &[u8; 32]) -> Result<()> {
66 if bool::from(k256::elliptic_curve::Field::is_zero(&digest_scalar(digest))) {
67 return Err(Error::InvalidEncoding);
68 }
69 Ok(())
70}
71
72pub(crate) fn signing_key(
75 x_pub: &ProjectivePoint,
76 hd: Option<&HdPath>,
77) -> Result<(ProjectivePoint, Scalar)> {
78 match hd {
79 None => Ok((*x_pub, Scalar::ZERO)),
80 Some(h) => {
81 let delta = h.delta(x_pub)?;
82 Ok((*x_pub + ProjectivePoint::GENERATOR * delta, delta))
83 }
84 }
85}
86
87pub(crate) fn tx_base(params: &Params, ssid: &Ssid, role: &str) -> Transcript {
89 let mut t = Transcript::new("sign");
90 t.append("params_id", &[params.id as u8]);
91 t.append("ssid", &ssid.encode());
92 t.append("role", role.as_bytes());
93 t
94}
95
96pub(crate) fn nonce_challenge(
98 params: &Params,
99 ssid: &Ssid,
100 x_pub: &ProjectivePoint,
101 r1: &ProjectivePoint,
102 r: &ProjectivePoint,
103 digest: &[u8; 32],
104) -> Scalar {
105 let mut t = Transcript::new("ro/sign-nonce");
106 t.append("params_id", &[params.id as u8]);
107 t.append("ssid", &ssid.encode());
108 t.append("X", &encode_point(x_pub));
109 t.append("R1", &encode_point(r1));
110 t.append("R", &encode_point(r));
111 t.append("m", digest);
112 t.challenge("e").scalar()
113}
114
115pub(crate) fn hedged_nonce<R: CryptoRng + ?Sized>(
117 rng: &mut R,
118 label: &str,
119 secret: &Scalar,
120 context: &[u8],
121) -> Scalar {
122 let mut fresh = [0u8; 32];
123 for i in 0u32.. {
124 rng.fill_bytes(&mut fresh);
125 let mut t = Transcript::new("hedged-nonce");
126 t.append("label", label.as_bytes());
127 t.append("rng", &fresh);
128 t.append("secret", &encode_scalar(secret));
129 t.append("ctx", context);
130 t.append("ctr", &i.to_be_bytes());
131 let k = t.challenge("k").scalar();
132 if !bool::from(k256::elliptic_curve::Field::is_zero(&k)) {
133 return k;
134 }
135 }
136 Scalar::ONE
137}
138
139pub(crate) fn ecdsa_verify(
141 x_pub: &ProjectivePoint,
142 digest: &[u8; 32],
143 sig: &Signature,
144) -> Result<()> {
145 let vk = VerifyingKey::from_affine(x_pub.to_affine()).map_err(|_| Error::VerificationFailed)?;
146 vk.verify_prehash(digest, &sig.to_ecdsa()?)
147 .map_err(|_| Error::VerificationFailed)
148}
149
150pub(crate) fn finalize(r_point: &ProjectivePoint, r: Scalar, s: Scalar) -> Signature {
152 let affine = r_point.to_affine();
153 let y_odd: bool = affine.y_is_odd().into();
154 let x_reduced = encode_scalar(&r) != affine.x().as_slice();
155 let mut recid = u8::from(y_odd) | (u8::from(x_reduced) << 1);
156 let mut s = s;
157 if bool::from(s.is_high()) {
158 s = -s;
159 recid ^= 1;
160 }
161 Signature { r, s, recid }
162}
163
164pub(crate) fn decode_signature(r: &[u8], s: &[u8], recid: u8) -> Result<Signature> {
166 let sig = Signature {
167 r: decode_scalar(r)?,
168 s: decode_scalar(s)?,
169 recid,
170 };
171 sig.recovery_id()?;
172 Ok(sig)
173}
174
175#[cfg(test)]
176mod tests {
177 extern crate std;
178 use super::*;
179 use crate::hd::HdPath;
180 use crate::{
181 keygen::{ClientKeyShare, ServerKeyShare},
182 testutil::{fx, keygen, params},
183 };
184 use k256::ecdsa::VerifyingKey;
185 use rand_chacha::ChaCha8Rng;
186 use rand_core::{Rng, SeedableRng};
187 use std::{collections::BTreeSet, time::Instant, vec::Vec};
188
189 fn sign_once(
191 rng: &mut ChaCha8Rng,
192 c: &ClientKeyShare,
193 s: &ServerKeyShare,
194 digest: &[u8; 32],
195 ) -> (Signature, Vec<u8>) {
196 let (s1, m1) =
197 ServerSign0::start(rng, &fx().server_view, s, b"client-1", digest, None).unwrap();
198 let m2 = ClientSign::respond(rng, &fx().setup, c, b"client-1", digest, &m1, None).unwrap();
199 s1.finish(&fx().server_view, fx().sk.paillier_sk(), &m2)
200 .unwrap()
201 }
202
203 fn digest(rng: &mut ChaCha8Rng) -> [u8; 32] {
204 let mut d = [0u8; 32];
205 rng.fill_bytes(&mut d);
206 d
207 }
208
209 #[test]
210 fn honest_signatures_verify_and_recover() {
211 let (c, s) = keygen(21);
212 let mut rng = ChaCha8Rng::seed_from_u64(1);
213 let vk = VerifyingKey::from_affine(c.public_key().to_affine()).unwrap();
214 let mut parities = BTreeSet::new();
215 for _ in 0..40 {
216 let d = digest(&mut rng);
217 let (sig, m3) = sign_once(&mut rng, &c, &s, &d);
218 vk.verify_prehash(&d, &sig.to_ecdsa().unwrap()).unwrap();
220 assert!(!bool::from(sig.s.is_high()));
222 let rec = VerifyingKey::recover_from_prehash(
224 &d,
225 &sig.to_ecdsa().unwrap(),
226 sig.recovery_id().unwrap(),
227 )
228 .unwrap();
229 assert_eq!(rec, vk);
230 assert_eq!(
232 ClientSign::verify_final(¶ms(), &c, &d, &m3, None).unwrap(),
233 sig
234 );
235 parities.insert(sig.recid & 1);
236 }
237 assert_eq!(parities.len(), 2, "both y parities must occur");
238 }
239
240 #[test]
241 fn same_digest_gives_fresh_nonces() {
242 let (c, s) = keygen(22);
243 let mut rng = ChaCha8Rng::seed_from_u64(2);
244 let d = digest(&mut rng);
245 let (a, _) = sign_once(&mut rng, &c, &s, &d);
246 let (b, _) = sign_once(&mut rng, &c, &s, &d);
247 assert_ne!(a.r, b.r);
248 }
249
250 #[test]
251 fn corrupted_messages_are_rejected() {
252 let (c, s) = keygen(23);
253 let mut rng = ChaCha8Rng::seed_from_u64(3);
254 let d = digest(&mut rng);
255 let (s1, m1) =
256 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d, None).unwrap();
257
258 for i in [4usize, 10, 30, 36, 50, m1.len() - 1] {
259 let mut bad = m1.clone();
260 bad[i] ^= 1;
261 assert!(
262 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-1", &d, &bad, None)
263 .is_err(),
264 "msg1 flip {i}"
265 );
266 }
267 let m2 =
268 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-1", &d, &m1, None).unwrap();
269 let positions = [
270 4usize,
271 20,
272 40,
273 80,
274 120,
275 200,
276 400,
277 m2.len() / 2,
278 m2.len() - 60,
279 m2.len() - 1,
280 ];
281 for i in positions.iter().copied().filter(|i| *i < m2.len()) {
282 let mut bad = m2.clone();
283 bad[i] ^= 1;
284 let r = s1
285 .test_clone()
286 .finish(&fx().server_view, fx().sk.paillier_sk(), &bad);
287 assert!(r.is_err(), "msg2 flip {i}");
288 }
289 assert!(
290 s1.test_clone()
291 .finish(
292 &fx().server_view,
293 fx().sk.paillier_sk(),
294 &m2[..m2.len() - 1]
295 )
296 .is_err()
297 );
298 assert!(
299 s1.finish(&fx().server_view, fx().sk.paillier_sk(), &m2)
300 .is_ok()
301 );
302 }
303
304 #[test]
305 fn session_binding() {
306 let (c, s) = keygen(24);
307 let (c_other, _) = keygen(25);
308 let mut rng = ChaCha8Rng::seed_from_u64(4);
309 let (d1, d2) = (digest(&mut rng), digest(&mut rng));
310
311 let (s1, m1) =
313 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d1, None).unwrap();
314 let m2 =
315 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-1", &d2, &m1, None).unwrap();
316 assert!(
317 s1.finish(&fx().server_view, fx().sk.paillier_sk(), &m2)
318 .is_err()
319 );
320
321 let (s1, m1) =
323 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d1, None).unwrap();
324 let m2 =
325 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-X", &d1, &m1, None).unwrap();
326 assert!(
327 s1.finish(&fx().server_view, fx().sk.paillier_sk(), &m2)
328 .is_err()
329 );
330
331 let (_, m1a) =
333 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d1, None).unwrap();
334 let m2a =
335 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-1", &d1, &m1a, None).unwrap();
336 let (s1b, _) =
337 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d1, None).unwrap();
338 assert!(
339 s1b.finish(&fx().server_view, fx().sk.paillier_sk(), &m2a)
340 .is_err()
341 );
342
343 let (_, m1) =
345 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d1, None).unwrap();
346 assert!(
347 ClientSign::respond(&mut rng, &fx().setup, &c_other, b"client-1", &d1, &m1, None)
348 .is_err()
349 );
350 }
351
352 fn hd(chain: u8, path: &[u32]) -> HdPath {
353 HdPath::new([chain; 32], path.to_vec()).unwrap()
354 }
355
356 #[test]
357 fn hd_signatures_verify_under_the_derived_key_only() {
358 let (c, s) = keygen(28);
359 let mut rng = ChaCha8Rng::seed_from_u64(7);
360 let x = VerifyingKey::from_affine(c.public_key().to_affine()).unwrap();
361 for path in [
362 hd(1, &[0]),
363 hd(2, &[44, 0, 0, 0, 7]),
364 hd(3, &[5, 6, 1_000_000]),
365 ] {
366 let d = digest(&mut rng);
367 let (st, m1) = ServerSign0::start(
368 &mut rng,
369 &fx().server_view,
370 &s,
371 b"client-1",
372 &d,
373 Some(&path),
374 )
375 .unwrap();
376 let m2 =
377 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-1", &d, &m1, Some(&path))
378 .unwrap();
379 let (sig, m3) = st
380 .finish(&fx().server_view, fx().sk.paillier_sk(), &m2)
381 .unwrap();
382
383 let derived = path.delta(c.public_key()).unwrap();
384 let derived_pk = VerifyingKey::from_affine(
385 (*c.public_key() + ProjectivePoint::GENERATOR * derived).to_affine(),
386 )
387 .unwrap();
388 let ecdsa = sig.to_ecdsa().unwrap();
389 derived_pk.verify_prehash(&d, &ecdsa).unwrap();
390 assert!(
391 x.verify_prehash(&d, &ecdsa).is_err(),
392 "must not verify under the master key"
393 );
394 let rec =
395 VerifyingKey::recover_from_prehash(&d, &ecdsa, sig.recovery_id().unwrap()).unwrap();
396 assert_eq!(rec, derived_pk);
397 assert_eq!(
398 ClientSign::verify_final(¶ms(), &c, &d, &m3, Some(&path)).unwrap(),
399 sig
400 );
401 assert!(ClientSign::verify_final(¶ms(), &c, &d, &m3, None).is_err());
402 }
403 }
404
405 #[test]
406 fn hd_paths_must_agree() {
407 let (c, s) = keygen(29);
408 let mut rng = ChaCha8Rng::seed_from_u64(8);
409 let d = digest(&mut rng);
410 let p = hd(1, &[1, 2]);
411 let (_, m1) =
412 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d, Some(&p)).unwrap();
413 for other in [Some(hd(1, &[2, 1])), Some(hd(9, &[1, 2])), None] {
415 let r = ClientSign::respond(
416 &mut rng,
417 &fx().setup,
418 &c,
419 b"client-1",
420 &d,
421 &m1,
422 other.as_ref(),
423 );
424 assert!(r.is_err());
425 }
426 let (_, m1) =
428 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d, None).unwrap();
429 assert!(
430 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-1", &d, &m1, Some(&p)).is_err()
431 );
432 }
433
434 #[test]
435 fn zero_digest_is_rejected_by_both_sides() {
436 let (c, s) = keygen(27);
437 let mut rng = ChaCha8Rng::seed_from_u64(6);
438 let zero = [0u8; 32];
439 assert!(
440 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &zero, None).is_err()
441 );
442 let order = crate::curve::order();
444 let mut n = [0u8; 32];
445 let bytes = order.to_be_bytes();
446 n.copy_from_slice(&bytes[bytes.len() - 32..]);
447 assert!(
448 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &n, None).is_err()
449 );
450 let d = digest(&mut rng);
451 let (_, m1) =
452 ServerSign0::start(&mut rng, &fx().server_view, &s, b"client-1", &d, None).unwrap();
453 assert!(
454 ClientSign::respond(&mut rng, &fx().setup, &c, b"client-1", &zero, &m1, None).is_err()
455 );
456 }
457
458 #[test]
459 fn signmsg3_is_checked_by_the_client() {
460 let (c, s) = keygen(26);
461 let mut rng = ChaCha8Rng::seed_from_u64(5);
462 let d = digest(&mut rng);
463 let (sig, m3) = sign_once(&mut rng, &c, &s, &d);
464 let p = params();
465 assert!(ClientSign::verify_final(&p, &c, &digest(&mut rng), &m3, None).is_err());
466 let mut bad = m3.clone();
467 let n = bad.len();
468 bad[n - 5] ^= 1; assert!(ClientSign::verify_final(&p, &c, &d, &bad, None).is_err());
470 let wrong_recid = crate::messages::SignMsg3 {
471 r: encode_scalar(&sig.r).to_vec(),
472 s: encode_scalar(&sig.s).to_vec(),
473 recid: sig.recid ^ 1,
474 }
475 .encode(&p)
476 .unwrap();
477 assert!(ClientSign::verify_final(&p, &c, &d, &wrong_recid, None).is_err());
478 }
479
480 #[test]
481 #[ignore = "long: run with --release --ignored; N from TECDSA_SIGN_COUNT (default 10000)"]
482 fn many_signatures_reduced() {
483 let n: usize = std::env::var("TECDSA_SIGN_COUNT")
484 .ok()
485 .and_then(|v| v.parse().ok())
486 .unwrap_or(10_000);
487 let (c, s) = keygen(30);
488 let vk = VerifyingKey::from_affine(c.public_key().to_affine()).unwrap();
489 let mut rng = ChaCha8Rng::seed_from_u64(30);
490 let t = Instant::now();
491 for _ in 0..n {
492 let d = digest(&mut rng);
493 let (sig, _) = sign_once(&mut rng, &c, &s, &d);
494 vk.verify_prehash(&d, &sig.to_ecdsa().unwrap()).unwrap();
495 let rec = VerifyingKey::recover_from_prehash(
496 &d,
497 &sig.to_ecdsa().unwrap(),
498 sig.recovery_id().unwrap(),
499 )
500 .unwrap();
501 assert_eq!(rec, vk);
502 }
503 std::eprintln!("{n} signatures in {:?}", t.elapsed());
504 }
505
506 #[test]
507 #[ignore = "full-size: run with --release --ignored --nocapture"]
508 fn full_size_sign() {
509 use crate::{params::PARAMS_128, setup::server_setup};
510 let p = PARAMS_128;
511 let mut rng = ChaCha8Rng::seed_from_u64(77);
512 let (data, sk) = server_setup(&mut rng, &p, [1u8; 32], b"server").unwrap();
513 let client_view = data.verify(&p).unwrap().with_tables();
514 let server_view = data.assume_verified(&p).unwrap().with_tables();
515 std::eprintln!("tables: {} KiB", client_view.tables_size_bytes() / 1024);
516 let clock = crate::testutil::TestClock(core::cell::Cell::new(0));
517 let (s1, m1) = crate::keygen::ServerKeygen0::start(&mut rng, &p, &data, b"client").unwrap();
518 let (c1, m2) =
519 crate::keygen::ClientKeygen0::on_msg1(&mut rng, &clock, &client_view, b"client", &m1)
520 .unwrap();
521 let (s_share, m3) = s1.on_msg2(&mut rng, &m2).unwrap();
522 let c_share = c1.on_msg3(&clock, &m3).unwrap();
523 let (mut tc, mut ts1, mut ts3) = (Vec::new(), Vec::new(), Vec::new());
524 let mut size2 = 0;
525 let count: usize = std::env::var("TECDSA_FULL_SIGN_COUNT")
526 .ok()
527 .and_then(|v| v.parse().ok())
528 .unwrap_or(20);
529 for _ in 0..count {
530 let d = digest(&mut rng);
531 let t = Instant::now();
532 let (st, m1) =
533 ServerSign0::start(&mut rng, &server_view, &s_share, b"client", &d, None).unwrap();
534 ts1.push(t.elapsed());
535 let t = Instant::now();
536 let m2 =
537 ClientSign::respond(&mut rng, &client_view, &c_share, b"client", &d, &m1, None)
538 .unwrap();
539 tc.push(t.elapsed());
540 size2 = m2.len();
541 let t = Instant::now();
542 let (sig, _) = st.finish(&server_view, sk.paillier_sk(), &m2).unwrap();
543 ts3.push(t.elapsed());
544 let vk = VerifyingKey::from_affine(c_share.public_key().to_affine()).unwrap();
545 vk.verify_prehash(&d, &sig.to_ecdsa().unwrap()).unwrap();
546 }
547 let med = |v: &mut Vec<core::time::Duration>| {
548 v.sort();
549 v[v.len() / 2]
550 };
551 std::eprintln!(
552 "sign (median of {count}): S1 {:?} | client {:?} (SignMsg2 {size2} B) | S3 {:?}",
553 med(&mut ts1),
554 med(&mut tc),
555 med(&mut ts3)
556 );
557 }
558}