Skip to content

Commit

Permalink
Use standard blind schnorr signatures (no pubkey tweak) (#2)
Browse files Browse the repository at this point in the history
* use standard form of blind schnorr signatures (no pubkey tweak)

* Add to docs and link Wagner paper

* we need alloc feature for Vec
  • Loading branch information
nickfarrow authored Jan 27, 2023
1 parent 2028bd3 commit c2c49de
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 74 deletions.
121 changes: 48 additions & 73 deletions schnorr_fun/src/blind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]: <https://www.iacr.org/archive/crypto2002/24420288/24420288.pdf>
//! [SuredBits article]: <https://suredbits.com/schnorr-applications-blind-signatures/>
//! [Blind Schnorr Signatures in the Algebraic Group Model]: <https://eprint.iacr.org/2019/877.pdf>
//!
//! # 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;
Expand Down Expand Up @@ -62,7 +70,7 @@
//! let blind_sessions: Vec<_> = pub_nonces
//! .iter()
//! .map(|pub_nonce| {
//! Blinder::blind(
//! blind::Blinder::blind(
//! *pub_nonce,
//! public_key,
//! message,
Expand All @@ -81,23 +89,22 @@
//!
//! // 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,
//! &mut rand::thread_rng(),
//! );
//!
//! // 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 => {}
//! }
Expand All @@ -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<OutputSize = U32> + Clone, NG>(
Expand All @@ -130,39 +137,24 @@ pub fn create_blinded_values<'a, H: Digest<OutputSize = U32> + Clone, NG>(
message: Message,
schnorr: Schnorr<H, NG>,
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,
Expand All @@ -177,10 +169,8 @@ pub fn create_blinded_values<'a, H: Digest<OutputSize = U32> + Clone, NG>(
pub fn unblind_signature(
blinded_signature: Scalar<Public, Zero>,
alpha: &Scalar<Secret, NonZero>,
challenge: &Scalar<Secret, NonZero>,
tweak: &Scalar<Secret, NonZero>,
) -> Scalar<Public, Zero> {
s!(blinded_signature + alpha + challenge * tweak).public()
s!(blinded_signature + alpha).public()
}

/// The tweaks used for blinding the nonce, public key, and challenge
Expand All @@ -191,8 +181,6 @@ pub struct BlindingTweaks {
pub alpha: Scalar,
/// tweak value beta
pub beta: Scalar,
/// tweak value t
pub t: Scalar,
}

impl BlindingTweaks {
Expand All @@ -201,16 +189,13 @@ impl BlindingTweaks {
BlindingTweaks {
alpha: Scalar::random(rng),
beta: Scalar::random(rng),
t: Scalar::random(rng),
}
}
}

/// 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
Expand Down Expand Up @@ -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,
Expand All @@ -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<Public, Zero>) -> 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,
Expand Down Expand Up @@ -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<R: RngCore + CryptoRng>(
pub fn blind_sign_multiple<R: RngCore + CryptoRng>(
secret: &Scalar,
nonces: Vec<Scalar>,
sig_requests: &mut Vec<SignatureRequest>,
Expand Down Expand Up @@ -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,
Expand All @@ -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 => {}
}
Expand Down Expand Up @@ -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));
}
}
}
2 changes: 1 addition & 1 deletion schnorr_fun/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit c2c49de

Please sign in to comment.