Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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