From 61b301705a866ec81946961147c98aba05e0ea5e Mon Sep 17 00:00:00 2001 From: Tibo-lg Date: Tue, 15 Sep 2020 10:46:19 +0900 Subject: [PATCH] Add Schnorr signatures module --- src/constants.rs | 9 +++ src/lib.rs | 10 +++ src/schnorrsig.rs | 196 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 src/schnorrsig.rs diff --git a/src/constants.rs b/src/constants.rs index cb185e4fc..c87526e2c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -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, diff --git a/src/lib.rs b/src/lib.rs index 4d887a0c1..d2c2aacc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,6 +169,7 @@ mod context; pub mod constants; pub mod ecdh; pub mod key; +pub mod schnorrsig; #[cfg(feature = "recovery")] pub mod recovery; @@ -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 { @@ -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", } } } diff --git a/src/schnorrsig.rs b/src/schnorrsig.rs new file mode 100644 index 000000000..ad230e735 --- /dev/null +++ b/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 { + 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 { + 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 Secp256k1 { + /// Create a schnorr signature. + pub fn schnorr_sign( + &self, + msg: &Message, + sk: &SecretKey, + aux_rand: &[u8; 32], + ) -> Result { + 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()); + } +}