Skip to content

Commit

Permalink
k256: add support for non-32-byte BIP340 signatures (#1041)
Browse files Browse the repository at this point in the history
This is a backport of #1041 onto the 0.13 branch, with some
small changes to accomodate SemVer.

Fixes #1040
  • Loading branch information
randombit authored and tarcieri committed Sep 20, 2024
1 parent 3787e4c commit 613cbaf
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 36 deletions.
63 changes: 62 additions & 1 deletion k256/src/schnorr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}",
Expand All @@ -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<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
33 changes: 22 additions & 11 deletions k256/src/schnorr/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -81,6 +81,20 @@ impl SigningKey {
msg_digest: &[u8; 32],
aux_rand: &[u8; 32],
) -> Result<Signature> {
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<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 +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)
Expand All @@ -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(),
);

Expand All @@ -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)
}
Expand Down Expand Up @@ -164,14 +178,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 +199,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 +215,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
65 changes: 41 additions & 24 deletions k256/src/schnorr/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <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,30 +104,7 @@ impl PrehashVerifier<Signature> 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 = <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

0 comments on commit 613cbaf

Please sign in to comment.