Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for BIP340 signtures where the messages are not exactly 32 bytes #1041

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion k256/src/schnorr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,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 {}",
Expand All @@ -288,6 +288,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<u8>,
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
Expand Down
27 changes: 11 additions & 16 deletions k256/src/schnorr/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,12 @@ impl SigningKey {
/// # ⚠️ Warning
///
/// This is a low-level interface intended only for unusual use cases
/// involving signing pre-hashed messages.
/// 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_prehash_with_aux_rand(
&self,
msg_digest: &[u8; 32],
aux_rand: &[u8; 32],
) -> Result<Signature> {
pub fn sign_raw(&self, msg: &[u8], aux_rand: &[u8; 32]) -> Result<Signature> {
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()) {
Expand All @@ -90,7 +88,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)
Expand All @@ -105,7 +103,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(),
);

Expand All @@ -114,7 +112,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)
}
Expand Down Expand Up @@ -164,14 +162,13 @@ where
D: Digest + FixedOutput<OutputSize = U32>,
{
fn try_sign_digest(&self, digest: D) -> Result<Signature> {
self.sign_prehash_with_aux_rand(&digest.finalize_fixed().into(), &Default::default())
self.sign_raw(&digest.finalize_fixed(), &Default::default())
}
}

impl PrehashSigner<Signature> for SigningKey {
fn sign_prehash(&self, prehash: &[u8]) -> Result<Signature> {
let prehash = prehash.try_into().map_err(|_| Error::new())?;
self.sign_prehash_with_aux_rand(&prehash, &Default::default())
self.sign_raw(prehash, &Default::default())
}
}

Expand All @@ -186,7 +183,7 @@ where
) -> Result<Signature> {
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)
}
}

Expand All @@ -202,12 +199,10 @@ impl RandomizedPrehashSigner<Signature> for SigningKey {
rng: &mut impl CryptoRngCore,
prehash: &[u8],
) -> Result<Signature> {
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)
}
}

Expand Down
61 changes: 39 additions & 22 deletions k256/src/schnorr/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,44 @@ 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 = <Scalar as Reduce<U256>>::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<Self> {
let maybe_affine_point = AffinePoint::decompact(FieldBytes::from_slice(bytes));
Expand Down Expand Up @@ -64,28 +102,7 @@ impl PrehashVerifier<Signature> for VerifyingKey {
prehash: &[u8],
tarcieri marked this conversation as resolved.
Show resolved Hide resolved
signature: &Signature,
) -> core::result::Result<(), Error> {
let prehash: [u8; 32] = prehash.try_into().map_err(|_| Error::new())?;
let (r, s) = signature.split();

let e = <Scalar as Reduce<U256>>::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)
}
}

Expand Down
Loading