Skip to content

Commit

Permalink
Add Schnorr signatures module
Browse files Browse the repository at this point in the history
  • Loading branch information
Tibo-lg committed Sep 15, 2020
1 parent c469295 commit a0f1800
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/constants.rs
Expand Up @@ -34,6 +34,15 @@ pub const MAX_SIGNATURE_SIZE: usize = 72;
/// The maximum size of a compact signature
pub const COMPACT_SIGNATURE_SIZE: usize = 64;

/// Size of a schnorr signature
pub const SCHNORR_SIGNATURE_SIZE: usize = 64;

/// Size of a x-only public key
pub const X_ONLY_PUBLIC_KEY_SIZE: usize = 64;

/// Size of a key pair
pub const KEY_PAIR_SIZE: usize = 96;

/// The Prime for the secp256k1 field element.
pub const FIELD_SIZE: [u8; 32] = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
Expand Down
10 changes: 10 additions & 0 deletions src/lib.rs
Expand Up @@ -169,6 +169,7 @@ mod context;
pub mod constants;
pub mod ecdh;
pub mod key;
pub mod schnorrsig;
#[cfg(feature = "recovery")]
pub mod recovery;

Expand Down Expand Up @@ -550,6 +551,12 @@ pub enum Error {
InvalidTweak,
/// Didn't pass enough memory to context creation with preallocated memory
NotEnoughMemory,
/// Bad Schnorr signature
InvalidSchnorrSignature,
/// Bad x-only public key
InvalidXOnlyPublicKey,
/// Bad key pair
InvalidKeyPair,
}

impl Error {
Expand All @@ -563,6 +570,9 @@ impl Error {
Error::InvalidRecoveryId => "secp: bad recovery id",
Error::InvalidTweak => "secp: bad tweak",
Error::NotEnoughMemory => "secp: not enough memory allocated",
Error::InvalidSchnorrSignature => "secp: malformed Schnorr signature",
Error::InvalidXOnlyPublicKey => "secp: malformed x-only public key",
Error::InvalidKeyPair => "secp: malformed key pair",
}
}
}
Expand Down
196 changes: 196 additions & 0 deletions src/schnorrsig.rs
@@ -0,0 +1,196 @@
//! # Schnorrsig
//! Support for Schnorr signatures.
//!

use super::{from_hex, Error};
use core::{fmt, str};
use ffi::{self, CPtr};
use {constants, PublicKey, Secp256k1, SecretKey};
use {Message, Signing};

/// Represents a schnorr signature.
pub struct SchnorrSignature([u8; constants::SCHNORR_SIGNATURE_SIZE]);
impl_array_newtype!(SchnorrSignature, u8, constants::SCHNORR_SIGNATURE_SIZE);
impl_pretty_debug!(SchnorrSignature);

impl fmt::LowerHex for SchnorrSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for ch in &self.0[..] {
write!(f, "{:02x}", ch)?;
}
Ok(())
}
}

impl fmt::Display for SchnorrSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(self, f)
}
}

impl str::FromStr for SchnorrSignature {
type Err = Error;
fn from_str(s: &str) -> Result<SchnorrSignature, Error> {
let mut res = [0; constants::SCHNORR_SIGNATURE_SIZE];
match from_hex(s, &mut res) {
Ok(constants::SCHNORR_SIGNATURE_SIZE) => {
SchnorrSignature::from_slice(&res[0..constants::SCHNORR_SIGNATURE_SIZE])
}
_ => Err(Error::InvalidSchnorrSignature),
}
}
}

impl SchnorrSignature {
/// Creates an SchnorrSignature directly from a slice
#[inline]
pub fn from_slice(data: &[u8]) -> Result<SchnorrSignature, Error> {
match data.len() {
constants::SCHNORR_SIGNATURE_SIZE => {
let mut ret = [0; constants::SCHNORR_SIGNATURE_SIZE];
ret[..].copy_from_slice(data);
Ok(SchnorrSignature(ret))
}
_ => Err(Error::InvalidSchnorrSignature),
}
}
}

impl<C: Signing> Secp256k1<C> {
/// Create a schnorr signature.
pub fn schnorr_sign(
&self,
msg: &Message,
sk: &SecretKey,
aux_rand: &[u8; 32],
) -> Result<SchnorrSignature, Error> {
unsafe {
let mut keypair = ffi::KeyPair::new();
let ret = ffi::secp256k1_keypair_create(self.ctx, &mut keypair, sk.as_c_ptr());
if ret == 0 {
return Err(Error::InvalidSecretKey);
}

let mut sig = [0u8; constants::SCHNORR_SIGNATURE_SIZE];
assert_eq!(
1,
ffi::secp256k1_schnorrsig_sign(
self.ctx,
sig.as_mut_c_ptr(),
msg.as_c_ptr(),
&keypair,
ffi::secp256k1_nonce_function_bip340,
aux_rand.as_c_ptr() as *const ffi::types::c_void
)
);

Ok(SchnorrSignature(sig))
}
}

/// Verify a schnorr signature.
pub fn schnorr_verify(
&self,
sig: &SchnorrSignature,
msg: &Message,
pubkey: &PublicKey,
) -> Result<(), Error> {
unsafe {
let mut xonly_pubkey = ffi::XOnlyPublicKey::new();
let mut pk_parity: ffi::types::c_int = 0;
let mut ret = ffi::secp256k1_xonly_pubkey_from_pubkey(
self.ctx,
&mut xonly_pubkey,
&mut pk_parity,
pubkey.as_c_ptr(),
);

if ret == 0 {
return Err(Error::InvalidPublicKey);
}

ret = ffi::secp256k1_schnorrsig_verify(
self.ctx,
sig.as_c_ptr(),
msg.as_c_ptr(),
&xonly_pubkey,
);

return if ret == 1 {
Ok(())
} else {
Err(Error::InvalidSchnorrSignature)
};
}
}
}

#[cfg(test)]
mod tests {
use super::super::{from_hex, Message, PublicKey, Secp256k1, SecretKey};
use super::SchnorrSignature;
use rand::thread_rng;
use rand::RngCore;
use std::str::FromStr;

macro_rules! hex_32 {
($hex:expr) => {{
let mut result = [0; 32];
from_hex($hex, &mut result).expect("valid hex string");
result
}};
}

#[test]
fn test_schnorr() {
let secp = Secp256k1::new();

let mut rng = thread_rng();
let (seckey, pubkey) = secp.generate_keypair(&mut rng);
let mut aux_rand = [0; 32];
let mut msg = [0; 32];

for _ in 0..100 {
rng.fill_bytes(&mut aux_rand);
rng.fill_bytes(&mut msg);
let msg = Message::from_slice(&msg).unwrap();

let sig = secp.schnorr_sign(&msg, &seckey, &aux_rand).unwrap();

assert!(secp.schnorr_verify(&sig, &msg, &pubkey).is_ok());
}
}

#[test]
fn test_schnorr_sign() {
let secp = Secp256k1::new();

let hex_msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
let msg = Message::from_slice(&hex_msg).unwrap();
let sk =
SecretKey::from_str("688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF")
.unwrap();
let aux_rand: [u8; 32] =
hex_32!("02CCE08E913F22A36C5648D6405A2C7C50106E7AA2F1649E381C7F09D16B80AB");
let expected_sig = SchnorrSignature::from_str("6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8").unwrap();

let sig = secp.schnorr_sign(&msg, &sk, &aux_rand).unwrap();

assert_eq!(expected_sig, sig);
}

#[test]
fn test_schnorr_verify() {
let secp = Secp256k1::new();

let hex_msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
let msg = Message::from_slice(&hex_msg).unwrap();
let sig = SchnorrSignature::from_str("6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8").unwrap();
let pubkey = PublicKey::from_str(
"02B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390",
)
.unwrap();

assert!(secp.schnorr_verify(&sig, &msg, &pubkey).is_ok());
}
}

0 comments on commit a0f1800

Please sign in to comment.