1use core::fmt;
31
32use k256::elliptic_curve::bigint::U512;
33use k256::elliptic_curve::ops::Reduce;
34use k256::{NonZeroScalar, ProjectivePoint, Scalar, WideBytes};
35use rand_core::CryptoRngCore;
36use sha2::{Digest, Sha256, Sha512};
37use subtle::ConstantTimeEq;
38use zeroize::{Zeroize, Zeroizing};
39
40use crate::encoding::{self, EncodingError};
41use crate::keys::{
42 CURVE_ORDER, KeyError, PrivateKey, PublicKey, finalize_wiped, lift_x, nonzero_scalar_from_bytes, reduce32,
43 scalar_to_bytes, x_mod_n,
44};
45
46pub use crate::keys::Signature;
47
48pub const NONCE_TAG: &[u8] = b"paralock/adaptor/nonce/v1";
50pub const DLEQ_NONCE_TAG: &[u8] = b"paralock/adaptor/dleq-nonce/v1";
52pub const DLEQ_TAG: &[u8] = b"paralock/adaptor/dleq/v1";
55pub const PRE_SIGNATURE_SIZE: usize = 162;
57
58const MAX_NONCE_ATTEMPTS: usize = 8;
61
62#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
64#[non_exhaustive]
65pub enum Error {
66 #[error(transparent)]
68 Encoding(#[from] EncodingError),
69 #[error(transparent)]
71 Key(#[from] KeyError),
72 #[error("adaptor secret is out of range")]
74 SecretOutOfRange,
75 #[error("pre-signature s is out of range")]
77 PreSignatureOutOfRange,
78 #[error("pre-signature R is not a valid point")]
80 InvalidPreSignaturePoint,
81 #[error("signature does not match the pre-signature")]
83 SignatureMismatch,
84 #[error("could not extract the adaptor secret")]
86 ExtractFailed,
87 #[error("signature does not recover to the public key")]
89 NoRecoveryId,
90 #[error("could not derive a nonce; the random number generator is broken")]
92 NonceGenerationFailed,
93 #[error("pre-signature does not verify")]
96 InvalidPreSignature,
97 #[error("adaptor secret does not match the adaptor point")]
99 WrongAdaptorSecret,
100}
101
102#[derive(Clone, PartialEq, Eq)]
104pub struct AdaptorSecret(PrivateKey);
105
106impl AdaptorSecret {
107 pub fn random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Self {
109 Self(PrivateKey::random(rng))
110 }
111
112 pub fn try_random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Result<Self, Error> {
114 PrivateKey::try_random(rng).map(Self).map_err(|_| Error::NonceGenerationFailed)
115 }
116
117 pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, Error> {
119 PrivateKey::from_bytes(bytes).map(Self).map_err(|_| Error::SecretOutOfRange)
120 }
121
122 pub fn from_hex(s: &str) -> Result<Self, Error> {
124 let bytes = Zeroizing::new(encoding::decode_hex(s)?);
125 let array = Zeroizing::new(encoding::to_array::<32>(&bytes, "adaptor secret")?);
126 Self::from_bytes(&array)
127 }
128
129 pub fn to_bytes(&self) -> Zeroizing<[u8; 32]> {
131 self.0.to_bytes()
132 }
133
134 pub fn to_hex(&self) -> Zeroizing<String> {
136 Zeroizing::new(encoding::to_hex(&*self.to_bytes()))
137 }
138
139 pub fn point(&self) -> PublicKey {
141 self.0.public_key()
142 }
143
144 fn scalar(&self) -> Zeroizing<Scalar> {
145 Zeroizing::new(*self.0.scalar())
146 }
147}
148
149impl ConstantTimeEq for AdaptorSecret {
150 fn ct_eq(&self, other: &Self) -> subtle::Choice {
151 self.to_bytes().ct_eq(&*other.to_bytes())
152 }
153}
154
155impl fmt::Debug for AdaptorSecret {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.write_str("AdaptorSecret(<redacted>)")
158 }
159}
160
161pub fn adaptor_key_pair<R: CryptoRngCore + ?Sized>(rng: &mut R) -> (AdaptorSecret, PublicKey) {
163 let secret = AdaptorSecret::random(rng);
164 let point = secret.point();
165 (secret, point)
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub struct DleqProof {
172 pub c: [u8; 32],
174 pub z: [u8; 32],
176}
177
178#[derive(Clone, Copy, PartialEq, Eq, Hash)]
181pub struct PreSignature {
182 pub r_point: [u8; 33],
184 pub rp_point: [u8; 33],
186 pub s: [u8; 32],
188 pub proof: DleqProof,
190}
191
192impl PreSignature {
193 pub fn encode(&self) -> [u8; PRE_SIGNATURE_SIZE] {
195 let mut out = [0u8; PRE_SIGNATURE_SIZE];
196 out[..33].copy_from_slice(&self.r_point);
197 out[33..66].copy_from_slice(&self.rp_point);
198 out[66..98].copy_from_slice(&self.s);
199 out[98..130].copy_from_slice(&self.proof.c);
200 out[130..].copy_from_slice(&self.proof.z);
201 out
202 }
203
204 pub fn to_hex(&self) -> String {
206 encoding::to_hex(&self.encode())
207 }
208
209 pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
211 let b: [u8; PRE_SIGNATURE_SIZE] = encoding::to_array(bytes, "pre-signature")?;
212 let mut pre =
213 Self { r_point: [0; 33], rp_point: [0; 33], s: [0; 32], proof: DleqProof { c: [0; 32], z: [0; 32] } };
214 pre.r_point.copy_from_slice(&b[..33]);
215 pre.rp_point.copy_from_slice(&b[33..66]);
216 pre.s.copy_from_slice(&b[66..98]);
217 pre.proof.c.copy_from_slice(&b[98..130]);
218 pre.proof.z.copy_from_slice(&b[130..]);
219 Ok(pre)
220 }
221
222 pub fn from_hex(s: &str) -> Result<Self, Error> {
224 Self::decode(&encoding::decode_hex(s)?)
225 }
226
227 pub fn r(&self) -> Result<[u8; 32], Error> {
229 Ok(scalar_to_bytes(&r_of(&self.r_point)?))
230 }
231}
232
233impl fmt::Debug for PreSignature {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 write!(f, "PreSignature({})", self.to_hex())
236 }
237}
238
239fn r_of(r_point: &[u8; 33]) -> Result<Scalar, Error> {
240 let point = PublicKey::parse(r_point, false, "pre-signature R").map_err(|_| Error::InvalidPreSignaturePoint)?;
241 Ok(x_mod_n(point.affine()))
242}
243
244fn hedged_nonce<R: CryptoRngCore + ?Sized>(
248 rng: &mut R,
249 tag: &[u8],
250 secret: &[u8; 32],
251 context: &[&[u8]],
252) -> Result<Zeroizing<NonZeroScalar>, Error> {
253 let mut hasher = Sha512::new();
254 for _ in 0..MAX_NONCE_ATTEMPTS {
255 let mut random = Zeroizing::new([0u8; 32]);
256 rng.try_fill_bytes(&mut *random).map_err(|_| Error::NonceGenerationFailed)?;
257 hasher.update(tag);
258 hasher.update(secret);
259 for part in context {
260 hasher.update(part);
261 }
262 hasher.update(random.as_slice());
263 let mut wide: WideBytes = finalize_wiped(&mut hasher);
264 let mut k = <Scalar as Reduce<U512>>::reduce_bytes(&wide);
265 wide.zeroize();
266 let nonzero = Option::<NonZeroScalar>::from(NonZeroScalar::new(k));
267 k.zeroize();
268 if let Some(k) = nonzero {
269 return Ok(Zeroizing::new(k));
270 }
271 }
272 Err(Error::NonceGenerationFailed)
273}
274
275fn compressed(point: &ProjectivePoint) -> Option<[u8; 33]> {
276 PublicKey::from_projective(point).map(|p| p.to_bytes())
277}
278
279fn challenge(points: &[&ProjectivePoint; 6]) -> Option<Scalar> {
281 let mut hasher = Sha256::new();
282 hasher.update(DLEQ_TAG);
283 for point in points {
284 hasher.update(compressed(point)?);
285 }
286 Some(reduce32(&hasher.finalize().into()))
287}
288
289fn dleq_prove<R: CryptoRngCore + ?Sized>(
291 rng: &mut R,
292 k: &NonZeroScalar,
293 a: &ProjectivePoint,
294 y: &ProjectivePoint,
295 b: &ProjectivePoint,
296) -> Result<DleqProof, Error> {
297 let (a_bytes, y_bytes, b_bytes) = (
298 compressed(a).ok_or(Error::NonceGenerationFailed)?,
299 compressed(y).ok_or(Error::NonceGenerationFailed)?,
300 compressed(b).ok_or(Error::NonceGenerationFailed)?,
301 );
302 let k_bytes = Zeroizing::new(scalar_to_bytes(k));
303 let t = hedged_nonce(rng, DLEQ_NONCE_TAG, &k_bytes, &[&a_bytes, &y_bytes, &b_bytes])?;
304 let g = ProjectivePoint::GENERATOR;
305 let c = challenge(&[&g, y, a, b, &(g * **t), &(*y * **t)]).ok_or(Error::NonceGenerationFailed)?;
306 let mut z = **t + c * **k;
307 let proof = DleqProof { c: scalar_to_bytes(&c), z: scalar_to_bytes(&z) };
308 z.zeroize();
309 Ok(proof)
310}
311
312fn below_order(bytes: &[u8; 32]) -> bool {
314 bytes.as_slice() < CURVE_ORDER.as_slice()
315}
316
317fn dleq_verify(a: &ProjectivePoint, y: &ProjectivePoint, b: &ProjectivePoint, proof: &DleqProof) -> bool {
318 if !below_order(&proof.c) || !below_order(&proof.z) {
319 return false;
320 }
321 let (c, z) = (reduce32(&proof.c), reduce32(&proof.z));
322 let g = ProjectivePoint::GENERATOR;
323 let t1 = g * z - *a * c;
324 let t2 = *y * z - *b * c;
325 match challenge(&[&g, y, a, b, &t1, &t2]) {
326 Some(expected) => bool::from(expected.ct_eq(&c)),
327 None => false,
328 }
329}
330
331pub fn pre_sign<R: CryptoRngCore + ?Sized>(
335 rng: &mut R,
336 private_key: &PrivateKey,
337 digest: &[u8; 32],
338 adaptor_point: &PublicKey,
339) -> Result<PreSignature, Error> {
340 let x = Zeroizing::new(private_key.scalar());
341 let x_bytes = Zeroizing::new(scalar_to_bytes(&x));
342 let m = reduce32(digest);
343 let m_bytes = scalar_to_bytes(&m);
344 let y_point = adaptor_point.projective();
345 let y_bytes = adaptor_point.to_bytes();
346 for _ in 0..MAX_NONCE_ATTEMPTS {
347 let k = hedged_nonce(rng, NONCE_TAG, &x_bytes, &[&m_bytes, &y_bytes])?;
348 let big_r = y_point * **k;
349 let Some(r_point) = PublicKey::from_projective(&big_r) else {
350 continue;
351 };
352 let r = x_mod_n(r_point.affine());
353 let k_inv = Zeroizing::new(Option::<Scalar>::from(k.invert()).unwrap_or(Scalar::ZERO));
354 let mut s = *k_inv * (m + r * **x);
355 if bool::from(r.is_zero()) || bool::from(s.is_zero()) {
356 continue;
357 }
358 let rp = ProjectivePoint::GENERATOR * **k;
359 let Some(rp_bytes) = compressed(&rp) else { continue };
360 let proof = dleq_prove(rng, &k, &rp, &y_point, &big_r)?;
361 let pre = PreSignature { r_point: r_point.to_bytes(), rp_point: rp_bytes, s: scalar_to_bytes(&s), proof };
362 s.zeroize();
363 return Ok(pre);
364 }
365 Err(Error::NonceGenerationFailed)
366}
367
368#[derive(Clone, Copy, PartialEq, Eq)]
373pub struct VerifiedPreSignature {
374 pre: PreSignature,
375 public_key: PublicKey,
376 digest: [u8; 32],
377 adaptor_point: PublicKey,
378}
379
380impl VerifiedPreSignature {
381 pub const fn pre_signature(&self) -> &PreSignature {
383 &self.pre
384 }
385
386 pub const fn public_key(&self) -> &PublicKey {
388 &self.public_key
389 }
390
391 pub const fn digest(&self) -> &[u8; 32] {
393 &self.digest
394 }
395
396 pub const fn adaptor_point(&self) -> &PublicKey {
398 &self.adaptor_point
399 }
400
401 pub fn to_hex(&self) -> String {
403 self.pre.to_hex()
404 }
405}
406
407impl fmt::Debug for VerifiedPreSignature {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 f.debug_struct("VerifiedPreSignature")
410 .field("pre", &self.pre)
411 .field("public_key", &self.public_key)
412 .field("digest", &encoding::to_hex(&self.digest))
413 .field("adaptor_point", &self.adaptor_point)
414 .finish()
415 }
416}
417
418pub fn pre_verify(
423 public_key: &PublicKey,
424 digest: &[u8; 32],
425 adaptor_point: &PublicKey,
426 pre: &PreSignature,
427) -> Result<VerifiedPreSignature, Error> {
428 if !pre_verifies(public_key, digest, adaptor_point, pre) {
429 return Err(Error::InvalidPreSignature);
430 }
431 Ok(VerifiedPreSignature { pre: *pre, public_key: *public_key, digest: *digest, adaptor_point: *adaptor_point })
432}
433
434fn pre_verifies(public_key: &PublicKey, digest: &[u8; 32], adaptor_point: &PublicKey, pre: &PreSignature) -> bool {
435 let (Ok(big_r), Ok(rp)) =
436 (PublicKey::parse(&pre.r_point, false, "R"), PublicKey::parse(&pre.rp_point, false, "R'"))
437 else {
438 return false;
439 };
440 let Some(s) = nonzero_scalar_from_bytes(&pre.s) else {
441 return false;
442 };
443 if !dleq_verify(&rp.projective(), &adaptor_point.projective(), &big_r.projective(), &pre.proof) {
444 return false;
445 }
446 let r = x_mod_n(big_r.affine());
447 if bool::from(r.is_zero()) {
448 return false;
449 }
450 let s_inv = Option::<Scalar>::from(s.invert()).unwrap_or(Scalar::ZERO);
451 let expected = ProjectivePoint::GENERATOR * (reduce32(digest) * s_inv) + public_key.projective() * (r * s_inv);
452 bool::from(expected.to_affine().ct_eq(rp.affine()))
453}
454
455pub fn adapt(pre: &VerifiedPreSignature, adaptor_secret: &AdaptorSecret) -> Result<Signature, Error> {
463 if adaptor_secret.point() != pre.adaptor_point {
464 return Err(Error::WrongAdaptorSecret);
465 }
466 let sig = hazmat::adapt_unchecked(&pre.pre, adaptor_secret)?;
467 if !verify(&pre.public_key, &pre.digest, &sig) {
469 return Err(Error::InvalidPreSignature);
470 }
471 Ok(sig)
472}
473
474pub mod hazmat {
476 use zeroize::Zeroizing;
477
478 use super::{AdaptorSecret, Error, PreSignature, Scalar, Signature, nonzero_scalar_from_bytes, r_of};
479 use crate::keys::scalar_to_bytes;
480
481 pub fn adapt_unchecked(pre: &PreSignature, adaptor_secret: &AdaptorSecret) -> Result<Signature, Error> {
486 let s_pre = nonzero_scalar_from_bytes(&pre.s).ok_or(Error::PreSignatureOutOfRange)?;
487 let r = r_of(&pre.r_point)?;
488 let y = adaptor_secret.scalar();
489 let y_inv = Zeroizing::new(Option::<Scalar>::from(y.invert()).unwrap_or(Scalar::ZERO));
490 let s = *s_pre * *y_inv;
491 let sig = Signature::from_scalars(scalar_to_bytes(&r), scalar_to_bytes(&s))?;
492 Ok(sig.normalize_s())
493 }
494}
495
496pub fn extract(pre: &PreSignature, sig: &Signature, adaptor_point: &PublicKey) -> Result<AdaptorSecret, Error> {
499 let r = r_of(&pre.r_point)?;
500 if !bool::from(r.ct_eq(&sig.r_scalar())) {
501 return Err(Error::SignatureMismatch);
502 }
503 let s_inv = Option::<Scalar>::from(sig.s_scalar().invert()).ok_or(Error::ExtractFailed)?;
504 let y = Zeroizing::new(reduce32(&pre.s) * s_inv);
505 for candidate in [*y, -*y] {
506 let mut candidate = Zeroizing::new(candidate);
507 if bool::from(candidate.is_zero()) {
508 continue;
509 }
510 let point = ProjectivePoint::GENERATOR * *candidate;
511 if bool::from(point.to_affine().ct_eq(adaptor_point.affine())) {
512 let bytes = Zeroizing::new(scalar_to_bytes(&candidate));
513 candidate.zeroize();
514 return AdaptorSecret::from_bytes(&bytes);
515 }
516 }
517 Err(Error::ExtractFailed)
518}
519
520pub fn verify(public_key: &PublicKey, digest: &[u8; 32], sig: &Signature) -> bool {
522 public_key.verify_prehash(digest, sig)
523}
524
525pub fn compact(sig: &Signature) -> [u8; 64] {
527 sig.to_compact()
528}
529
530pub fn der(sig: &Signature) -> Vec<u8> {
532 sig.to_der()
533}
534
535pub fn recovery_id(public_key: &PublicKey, digest: &[u8; 32], sig: &Signature) -> Result<u8, Error> {
537 let r = sig.r_scalar();
538 let r_inv = Option::<Scalar>::from(r.invert()).ok_or(Error::NoRecoveryId)?;
539 let m = reduce32(digest);
540 for bit in [0u8, 1] {
541 let Some(big_r) = lift_x(&sig.r(), bit == 1) else {
542 continue;
543 };
544 let q = (ProjectivePoint::from(big_r) * (sig.s_scalar() * r_inv)) - ProjectivePoint::GENERATOR * (m * r_inv);
545 if PublicKey::from_projective(&q).is_some_and(|q| q == *public_key) {
546 return Ok(bit);
547 }
548 }
549 Err(Error::NoRecoveryId)
550}
551
552pub fn recover_public_key(digest: &[u8; 32], sig: &Signature, recovery_id: u8) -> Result<PublicKey, Error> {
554 if recovery_id > 1 {
555 return Err(Error::NoRecoveryId);
556 }
557 let r_inv = Option::<Scalar>::from(sig.r_scalar().invert()).ok_or(Error::NoRecoveryId)?;
558 let big_r = lift_x(&sig.r(), recovery_id == 1).ok_or(Error::NoRecoveryId)?;
559 let m = reduce32(digest);
560 let q = (ProjectivePoint::from(big_r) * (sig.s_scalar() * r_inv)) - ProjectivePoint::GENERATOR * (m * r_inv);
561 PublicKey::from_projective(&q).ok_or(Error::NoRecoveryId)
562}