From 613cbafd63763c579922ad2b79fa041b4a0dde50 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Wed, 24 Apr 2024 10:12:55 -0400 Subject: [PATCH] k256: add support for non-32-byte BIP340 signatures (#1041) This is a backport of #1041 onto the 0.13 branch, with some small changes to accomodate SemVer. Fixes #1040 --- k256/src/schnorr.rs | 63 ++++++++++++++++++++++++++++++++- k256/src/schnorr/signing.rs | 33 ++++++++++++------ k256/src/schnorr/verifying.rs | 65 ++++++++++++++++++++++------------- 3 files changed, 125 insertions(+), 36 deletions(-) diff --git a/k256/src/schnorr.rs b/k256/src/schnorr.rs index b8439d80..15a99577 100644 --- a/k256/src/schnorr.rs +++ b/k256/src/schnorr.rs @@ -270,7 +270,7 @@ mod tests { assert_eq!(sk.verifying_key().to_bytes().as_slice(), &vector.public_key); let sig = sk - .sign_prehash_with_aux_rand(&vector.message, &vector.aux_rand) + .sign_raw(&vector.message, &vector.aux_rand) .unwrap_or_else(|_| { panic!( "low-level Schnorr signing failure for index {}", @@ -287,6 +287,67 @@ mod tests { } } + #[test] + fn bip340_ext_sign_vectors() { + // Test indexes 15-18 from https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv + // + // These tests all use the same key and aux + let sk = SigningKey::from_bytes(&hex!( + "0340034003400340034003400340034003400340034003400340034003400340" + )) + .unwrap(); + + let aux_rand = [0u8; 32]; + + struct Bip340ExtTest { + index: usize, + msg: alloc::vec::Vec, + signature: [u8; 64], + } + + let bip340_ext_sign_vectors = [ + Bip340ExtTest { + index: 15, + msg: vec![], + signature: hex!( + "71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF + 6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63" + ) + }, + Bip340ExtTest { + index: 16, + msg: hex!("11").to_vec(), + signature: hex!("08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF") + }, + Bip340ExtTest { + index: 17, + msg: hex!("0102030405060708090A0B0C0D0E0F1011").to_vec(), + signature: hex!("5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5"), + }, + Bip340ExtTest { + index: 18, + msg: vec![0x99; 100], + signature: hex!("403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367"), + }, + ]; + + for vector in bip340_ext_sign_vectors { + let sig = sk.sign_raw(&vector.msg, &aux_rand).unwrap_or_else(|_| { + panic!( + "low-level Schnorr signing failure for index {}", + vector.index + ) + }); + + assert_eq!( + vector.signature, + sig.to_bytes(), + "wrong signature for index {}", + vector.index + ); + } + } + /// Verification test vector struct VerifyVector { /// Index of test case diff --git a/k256/src/schnorr/signing.rs b/k256/src/schnorr/signing.rs index ecfdfa0a..0dff97a6 100644 --- a/k256/src/schnorr/signing.rs +++ b/k256/src/schnorr/signing.rs @@ -70,7 +70,7 @@ impl SigningKey { /// Compute Schnorr signature. /// - /// # ⚠️ Warning + /// # ⚠️ Warning /// /// This is a low-level interface intended only for unusual use cases /// involving signing pre-hashed messages. @@ -81,6 +81,20 @@ impl SigningKey { msg_digest: &[u8; 32], aux_rand: &[u8; 32], ) -> Result { + self.sign_raw(&msg_digest[..], &aux_rand) + } + + /// Compute Schnorr signature. + /// + /// # ⚠️ Warning + /// + /// This is a low-level interface intended only for unusual use cases + /// involving signing pre-hashed messages, or "raw" messages where the + /// message is not hashed at all prior to being used to generate the + /// Schnorr signature. + /// + /// The preferred interfaces are the [`Signer`] or [`RandomizedSigner`] traits. + pub fn sign_raw(&self, msg: &[u8], aux_rand: &[u8; 32]) -> Result { let mut t = tagged_hash(AUX_TAG).chain_update(aux_rand).finalize(); for (a, b) in t.iter_mut().zip(self.secret_key.to_bytes().iter()) { @@ -90,7 +104,7 @@ impl SigningKey { let rand = tagged_hash(NONCE_TAG) .chain_update(t) .chain_update(self.verifying_key.as_affine().x.to_bytes()) - .chain_update(msg_digest) + .chain_update(msg) .finalize(); let k = NonZeroScalar::try_from(&*rand) @@ -105,7 +119,7 @@ impl SigningKey { &tagged_hash(CHALLENGE_TAG) .chain_update(r.to_bytes()) .chain_update(self.verifying_key.to_bytes()) - .chain_update(msg_digest) + .chain_update(msg) .finalize(), ); @@ -114,7 +128,7 @@ impl SigningKey { let sig = Signature { r, s }; #[cfg(debug_assertions)] - self.verifying_key.verify_prehash(msg_digest, &sig)?; + self.verifying_key.verify_prehash(msg, &sig)?; Ok(sig) } @@ -164,14 +178,13 @@ where D: Digest + FixedOutput, { fn try_sign_digest(&self, digest: D) -> Result { - self.sign_prehash_with_aux_rand(&digest.finalize_fixed().into(), &Default::default()) + self.sign_raw(&digest.finalize_fixed(), &Default::default()) } } impl PrehashSigner for SigningKey { fn sign_prehash(&self, prehash: &[u8]) -> Result { - let prehash = prehash.try_into().map_err(|_| Error::new())?; - self.sign_prehash_with_aux_rand(&prehash, &Default::default()) + self.sign_raw(prehash, &Default::default()) } } @@ -186,7 +199,7 @@ where ) -> Result { let mut aux_rand = [0u8; 32]; rng.fill_bytes(&mut aux_rand); - self.sign_prehash_with_aux_rand(&digest.finalize_fixed().into(), &aux_rand) + self.sign_raw(&digest.finalize_fixed(), &aux_rand) } } @@ -202,12 +215,10 @@ impl RandomizedPrehashSigner for SigningKey { rng: &mut impl CryptoRngCore, prehash: &[u8], ) -> Result { - let prehash = prehash.try_into().map_err(|_| Error::new())?; - let mut aux_rand = [0u8; 32]; rng.fill_bytes(&mut aux_rand); - self.sign_prehash_with_aux_rand(&prehash, &aux_rand) + self.sign_raw(prehash, &aux_rand) } } diff --git a/k256/src/schnorr/verifying.rs b/k256/src/schnorr/verifying.rs index e35130e2..699c7113 100644 --- a/k256/src/schnorr/verifying.rs +++ b/k256/src/schnorr/verifying.rs @@ -35,6 +35,46 @@ impl VerifyingKey { self.as_affine().x.to_bytes() } + /// Compute Schnorr signature. + /// + /// # ⚠️ Warning + /// + /// This is a low-level interface intended only for unusual use cases + /// involving verifying pre-hashed messages, or "raw" messages where the + /// message is not hashed at all prior to being used to generate the + /// Schnorr signature. + /// + /// The preferred interfaces are the [`DigestVerifier`] or [`PrehashVerifier`] traits. + pub fn verify_raw( + &self, + message: &[u8], + signature: &Signature, + ) -> core::result::Result<(), Error> { + let (r, s) = signature.split(); + + let e = >::reduce_bytes( + &tagged_hash(CHALLENGE_TAG) + .chain_update(signature.r.to_bytes()) + .chain_update(self.to_bytes()) + .chain_update(message) + .finalize(), + ); + + let R = ProjectivePoint::lincomb( + &ProjectivePoint::GENERATOR, + s, + &self.inner.to_projective(), + &-e, + ) + .to_affine(); + + if R.is_identity().into() || R.y.normalize().is_odd().into() || R.x.normalize() != *r { + return Err(Error::new()); + } + + Ok(()) + } + /// Parse verifying key from big endian-encoded x-coordinate. pub fn from_bytes(bytes: &[u8]) -> Result { let maybe_affine_point = AffinePoint::decompact(FieldBytes::from_slice(bytes)); @@ -64,30 +104,7 @@ impl PrehashVerifier for VerifyingKey { prehash: &[u8], signature: &Signature, ) -> core::result::Result<(), Error> { - let prehash: [u8; 32] = prehash.try_into().map_err(|_| Error::new())?; - let (r, s) = signature.split(); - - let e = >::reduce_bytes( - &tagged_hash(CHALLENGE_TAG) - .chain_update(signature.r.to_bytes()) - .chain_update(self.to_bytes()) - .chain_update(prehash) - .finalize(), - ); - - let R = ProjectivePoint::lincomb( - &ProjectivePoint::GENERATOR, - s, - &self.inner.to_projective(), - &-e, - ) - .to_affine(); - - if R.is_identity().into() || R.y.normalize().is_odd().into() || R.x.normalize() != *r { - return Err(Error::new()); - } - - Ok(()) + self.verify_raw(prehash, signature) } }