diff --git a/schnorr_fun/src/blind.rs b/schnorr_fun/src/blind.rs index c51e972e..036ca639 100644 --- a/schnorr_fun/src/blind.rs +++ b/schnorr_fun/src/blind.rs @@ -2,27 +2,35 @@ //! //! Produce a schnorr signature where the signer does not know what they have signed. //! -//! A blind signing server (with public key `X = x*G`) sends a public nonce (`R = k*G`) to the user who wants a message signed. -//! The user blinds this nonce (`R' = R + alpha*G + beta*X`) as well as the server's public key by adding a random tweak (`X' = X + t*G`). +//! ⚠ When running multiple sessions in parallel a signing server must use `blind_sign_multiple` +//! which will randomly fail on 1 out of N signing requests. This is to prevent [Wagner attack]s, +//! where parallel signing sessions can allow for a forgery. //! -//! A challenge for the message (`M`) is created using these blinded values (`c = H[R'|X'|M]`), and this challenge is then blinded -//! also (`c' = c + beta`). The blinded challenge is sent to the signing server and is signed (`s = k + c'*x`). +//! # Summary //! -//! Note that when running multiple sessions in parallel a signing server should use `safe_blind_sign_multiple` which will randomly -//! fail on 1 out of N signing requests. This is to prevent Wagner attacks where parallel signing sessions allow for a forgery. +//! A blind signing server (with public key `X = x*G`) sends a public nonce (`R = k*G`) to a user +//! who wants to have a message signed. This user generates two random scalars (alpha, beta) and +//! uses them to blinds the signing server's nonce (`R' = R + alpha*G + beta*X`). //! -//! Once the user recieves the blinded signature, they can unblind it (`s' = s + alpha + c*t`). -//! The unblinded signature (`s', R'`) is a valid schnorr signature under the tweaked public key (`X'`). -//! The signer can not correlate this signature-nonce pair even if they know the tweaked public key, signature, message, and nonce. +//! The user then creates challenge for some message (`M`) they want signed, using these blinding +//! values (`c = H[R'|X|M]`), and then this challenge is then blinded itself also (`c' = c + beta`). +//! The blinded challenge is sent to the signing server who then signs it (`s = k + c'*x`). //! -//! This implementation mostly follows this [SuredBits article] and [Blind Schnorr Signatures in the Algebraic Group Model]. +//! Once the user recieves the blinded signature, they can unblind it (`s' = s + alpha). +//! The unblinded signature (`s', R'`) is a valid schnorr signature under the public key (`X`). +//! The signer can not correlate this signature-nonce pair even if they know the public key, +//! signature, message, and nonce. //! +//! This implementation was helped a lot by this [SuredBits article] and follows security fixes from +//! [Blind Schnorr Signatures in the Algebraic Group Model]. +//! +//! [Wagner attack]: //! [SuredBits article]: //! [Blind Schnorr Signatures in the Algebraic Group Model]: //! //! # Synopsis //! ``` -//! use schnorr_fun::{blind, Blinder, Message, Schnorr, nonce}; +//! use schnorr_fun::{blind, Message, Schnorr, nonce}; //! use secp256kfun::{g, marker::Public, Scalar, G, derive_nonce, nonce::Deterministic}; //! use rand::rngs::ThreadRng; //! use sha2::Sha256; @@ -62,7 +70,7 @@ //! let blind_sessions: Vec<_> = pub_nonces //! .iter() //! .map(|pub_nonce| { -//! Blinder::blind( +//! blind::Blinder::blind( //! *pub_nonce, //! public_key, //! message, @@ -81,7 +89,7 @@ //! //! // The blind signer server signs under their secret key and with the corresponding nonce for each //! // respective signature request -//! let session_signatures = blind::safe_blind_sign_multiple( +//! let session_signatures = blind::blind_sign_multiple( //! &secret, //! nonces, //! &mut signature_requests, @@ -89,15 +97,14 @@ //! ); //! //! // We iterate over our signing sessions, unblinding the session which completed. -//! // This reveals an uncorrelated signature for the message that is valid under the tweaked pubkey. +//! // This reveals an uncorrelated signature for the message that is valid under the pubkey. //! // The server has also not seen the nonce for this signature. //! for (blind_session, blind_signature) in blind_sessions.iter().zip(session_signatures) { //! match blind_signature { //! Some(blind_signature) => { //! let unblinded_signature = blind_session.unblind(blind_signature); -//! // Validate the unblinded signature against the tweaked public key -//! let (verification_pubkey, _) = blind_session.tweaked_pubkey.into_point_with_even_y(); -//! assert!(schnorr.verify(&verification_pubkey, message, &unblinded_signature)); +//! // Validate the unblinded signature against the public key +//! assert!(schnorr.verify(&public_key, message, &unblinded_signature)); //! } //! None => {} //! } @@ -121,7 +128,7 @@ use secp256kfun::{ /// /// # Returns /// -/// A tweaked_pubkey, blinded_nonce, and a blinded_challenge; +/// A blinded_nonce and a blinded_challenge; /// Also returns a needs_negation for the blinded public key and nonce /// The [`BlindingTweaks`] values (alpha, beta, t) may be negated to ensure even y values. pub fn create_blinded_values<'a, H: Digest + Clone, NG>( @@ -130,39 +137,24 @@ pub fn create_blinded_values<'a, H: Digest + Clone, NG>( message: Message, schnorr: Schnorr, blinding_tweaks: &mut BlindingTweaks, -) -> (Point, Point, Scalar, bool) { - let tweaked_pubkey = g!(public_key + blinding_tweaks.t * G) +) -> (Point, Scalar, bool) { + let blinded_nonce = g!(nonce + blinding_tweaks.alpha * G + blinding_tweaks.beta * public_key) .normalize() .non_zero() .expect("added tweak is random"); - let tweaked_pubkey_needs_negation = !tweaked_pubkey.is_y_even(); - let tweaked_pubkey = tweaked_pubkey.conditional_negate(tweaked_pubkey_needs_negation); - - let blinded_nonce = - g!(nonce + blinding_tweaks.alpha * G + blinding_tweaks.beta * tweaked_pubkey) - .normalize() - .non_zero() - .expect("added tweak is random"); - // we're actually going to discard these tweaks if the blinded nonce does need negation, // if we assert that we sample an even blinded nonce, then we have less to communicate let (xonly_blinded_nonce, blinded_nonce_needs_negation) = blinded_nonce.into_point_with_even_y(); - let (xonly_tweaked_pubkey, _) = tweaked_pubkey.into_point_with_even_y(); - - let mut blinded_challenge = - s!( - { schnorr.challenge(&xonly_blinded_nonce, &xonly_tweaked_pubkey, message,) } - + blinding_tweaks.beta - ) - .non_zero() - .expect("added tweak is random"); - blinded_challenge.conditional_negate(tweaked_pubkey_needs_negation); + let blinded_challenge = s!( + { schnorr.challenge(&xonly_blinded_nonce, &public_key, message,) } + blinding_tweaks.beta + ) + .non_zero() + .expect("added tweak is random"); ( - tweaked_pubkey, blinded_nonce, blinded_challenge, blinded_nonce_needs_negation, @@ -177,10 +169,8 @@ pub fn create_blinded_values<'a, H: Digest + Clone, NG>( pub fn unblind_signature( blinded_signature: Scalar, alpha: &Scalar, - challenge: &Scalar, - tweak: &Scalar, ) -> Scalar { - s!(blinded_signature + alpha + challenge * tweak).public() + s!(blinded_signature + alpha).public() } /// The tweaks used for blinding the nonce, public key, and challenge @@ -191,8 +181,6 @@ pub struct BlindingTweaks { pub alpha: Scalar, /// tweak value beta pub beta: Scalar, - /// tweak value t - pub t: Scalar, } impl BlindingTweaks { @@ -201,7 +189,6 @@ impl BlindingTweaks { BlindingTweaks { alpha: Scalar::random(rng), beta: Scalar::random(rng), - t: Scalar::random(rng), } } } @@ -209,8 +196,6 @@ impl BlindingTweaks { /// Blinder holds a blinded signature context which is later used to unblind the signature #[derive(Debug)] pub struct Blinder { - /// blinded public key X' = X + t*G - pub tweaked_pubkey: Point, /// tweaked public nonce R' = R + alpha*G + beta * X pub blinded_nonce: Point, /// tweaked challenge c' = c + beta @@ -239,18 +224,16 @@ impl Blinder { ) -> Self { loop { let mut blinding_tweaks = BlindingTweaks::new(rng); - let (tweaked_pubkey, blinded_nonce, blinded_challenge, nonce_needs_negation) = - create_blinded_values( - pubnonce, - public_key, - message, - schnorr.clone(), - &mut blinding_tweaks, - ); + let (blinded_nonce, blinded_challenge, nonce_needs_negation) = create_blinded_values( + pubnonce, + public_key, + message, + schnorr.clone(), + &mut blinding_tweaks, + ); if !nonce_needs_negation { break Blinder { - tweaked_pubkey, blinded_nonce, challenge: blinded_challenge, blinding_tweaks, @@ -263,14 +246,9 @@ impl Blinder { /// /// # Returns /// - /// A schnorr signature that should be valid under the tweaked public key and blinded nonce + /// A schnorr signature that should be valid under the public key and blinded nonce pub fn unblind(&self, blinded_signature: Scalar) -> Signature { - let sig = unblind_signature( - blinded_signature, - &self.blinding_tweaks.alpha, - &self.challenge, - &self.blinding_tweaks.t, - ); + let sig = unblind_signature(blinded_signature, &self.blinding_tweaks.alpha); Signature { s: sig, R: self.blinded_nonce.into_point_with_even_y().0, @@ -317,7 +295,7 @@ pub fn blind_sign( /// /// Disconnects 1 out of N times if there is N > 1 SignatureRequests supplied. /// Does not disconnect if only supplied one SignatureRequest -pub fn safe_blind_sign_multiple( +pub fn blind_sign_multiple( secret: &Scalar, nonces: Vec, sig_requests: &mut Vec, @@ -410,7 +388,7 @@ mod test { // The blind signer server signs under their secret key and with the corresponding nonce for each // respective signature request assert_eq!(signature_requests.len(), n_sessions); - let session_signatures = safe_blind_sign_multiple( + let session_signatures = blind_sign_multiple( &secret, nonces, &mut signature_requests, @@ -419,17 +397,15 @@ mod test { dbg!(&session_signatures); // We recieve an option of the blinded signature from the signer, and unblind it revealing - // an uncorrelated signature for the message that is valid under the tweaked pubkey. + // an uncorrelated signature for the message that is valid under the pubkey. // The server has also not seen the nonce for this signature. for (blind_session, blind_signature) in blind_sessions.iter().zip(session_signatures) { match blind_signature { Some(blind_signature) => { let unblinded_signature = blind_session.unblind(blind_signature); - // Validate the unblinded signature against the tweaked public key - let (verification_pubkey, _) = - blind_session.tweaked_pubkey.into_point_with_even_y(); - assert!(schnorr.verify(&verification_pubkey, message, &unblinded_signature)); + // Validate the unblinded signature against the public key + assert!(schnorr.verify(&public_key, message, &unblinded_signature)); } None => {} } @@ -470,8 +446,7 @@ mod test { let unblinded_signature = blind_session.unblind(blind_signature); - let (verification_pubkey, _) = blind_session.tweaked_pubkey.into_point_with_even_y(); - assert!(schnorr.verify(&verification_pubkey, message, &unblinded_signature)); + assert!(schnorr.verify(&public_key, message, &unblinded_signature)); } } } diff --git a/schnorr_fun/src/lib.rs b/schnorr_fun/src/lib.rs index 05e4ed4a..d08f41e8 100755 --- a/schnorr_fun/src/lib.rs +++ b/schnorr_fun/src/lib.rs @@ -28,10 +28,10 @@ pub mod binonce; #[cfg(feature = "alloc")] pub mod musig; +#[cfg(feature = "alloc")] pub mod blind; #[cfg(feature = "alloc")] pub mod frost; -pub use blind::*; mod signature; pub use signature::Signature;