Skip to content

Commit

Permalink
Update signature API for challenge slice lengths
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronFeickert committed Sep 25, 2023
1 parent 6c00a5d commit 8dde021
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 71 deletions.
6 changes: 3 additions & 3 deletions benches/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn sign_message(c: &mut Criterion) {
b.iter_batched(
gen_keypair,
|d| {
let _sig = RistrettoSchnorr::sign_message(&d.k, d.m, &mut OsRng).unwrap();
let _sig = RistrettoSchnorr::sign(&d.k, d.m, &mut OsRng).unwrap();
},
BatchSize::SmallInput,
);
Expand All @@ -60,10 +60,10 @@ fn verify_message(c: &mut Criterion) {
b.iter_batched(
|| {
let d = gen_keypair();
let s = RistrettoSchnorr::sign_message(&d.k, d.m, &mut OsRng).unwrap();
let s = RistrettoSchnorr::sign(&d.k, d.m, &mut OsRng).unwrap();
(d, s)
},
|(d, s)| assert!(s.verify_message(&d.p, d.m)),
|(d, s)| assert!(s.verify(&d.p, d.m)),
BatchSize::SmallInput,
);
});
Expand Down
37 changes: 18 additions & 19 deletions src/ristretto/ristretto_sig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use crate::{
/// let (k, P) = get_keypair();
/// let msg = "Small Gods";
/// let mut rng = thread_rng();
/// let sig = RistrettoSchnorr::sign_message(&k, &msg, &mut rng);
/// let sig = RistrettoSchnorr::sign(&k, &msg, &mut rng);
/// ```
///
/// # Verifying signatures
Expand All @@ -84,8 +84,8 @@ use crate::{
/// let P = RistrettoPublicKey::from_secret_key(&k);
/// let mut rng = thread_rng();
/// let sig: SchnorrSignature<RistrettoPublicKey, RistrettoSecretKey> =
/// SchnorrSignature::sign_message(&k, msg, &mut rng).unwrap();
/// assert!(sig.verify_message(&P, msg));
/// SchnorrSignature::sign(&k, msg, &mut rng).unwrap();
/// assert!(sig.verify(&P, msg));
/// ```
pub type RistrettoSchnorr = SchnorrSignature<RistrettoPublicKey, RistrettoSecretKey, SchnorrSigChallenge>;

Expand Down Expand Up @@ -116,8 +116,8 @@ pub type RistrettoSchnorr = SchnorrSignature<RistrettoPublicKey, RistrettoSecret
/// let P = RistrettoPublicKey::from_secret_key(&k);
/// let mut rng = thread_rng();
/// let sig: SchnorrSignature<RistrettoPublicKey, RistrettoSecretKey, MyCustomDomain> =
/// SchnorrSignature::sign_message(&k, msg, &mut rng).unwrap();
/// assert!(sig.verify_message(&P, msg));
/// SchnorrSignature::sign(&k, msg, &mut rng).unwrap();
/// assert!(sig.verify(&P, msg));
/// ```
pub type RistrettoSchnorrWithDomain<H> = SchnorrSignature<RistrettoPublicKey, RistrettoSecretKey, H>;

Expand Down Expand Up @@ -164,16 +164,16 @@ mod test {
.finalize();
let e_key = RistrettoSecretKey::from_bytes_wide(&e).unwrap();
let s = &r + &e_key * &k;
let sig = RistrettoSchnorr::sign_raw(&k, r, &e).unwrap();
let sig = RistrettoSchnorr::sign_raw_wide(&k, r, &e).unwrap();
let R_calc = sig.get_public_nonce();
assert_eq!(R, *R_calc);
assert_eq!(sig.get_signature(), &s);
assert!(sig.verify_challenge(&P, &e));
assert!(sig.verify_raw_wide(&P, &e));
// Doesn't work for invalid credentials
assert!(!sig.verify_challenge(&R, &e));
assert!(!sig.verify_raw_wide(&R, &e));
// Doesn't work for different challenge
let wrong_challenge = Blake2b::<U64>::digest(b"Guards! Guards!");
assert!(!sig.verify_challenge(&P, &wrong_challenge));
assert!(!sig.verify_raw_wide(&P, &wrong_challenge));
}

/// This test checks that the linearity of Schnorr signatures hold, i.e. that s = s1 + s2 is validated by R1 + R2
Expand All @@ -196,13 +196,13 @@ mod test {
.chain_update(b"Moving Pictures")
.finalize();
// Calculate Alice's signature
let s1 = RistrettoSchnorr::sign_raw(&k1, r1, &e).unwrap();
let s1 = RistrettoSchnorr::sign_raw_wide(&k1, r1, &e).unwrap();
// Calculate Bob's signature
let s2 = RistrettoSchnorr::sign_raw(&k2, r2, &e).unwrap();
let s2 = RistrettoSchnorr::sign_raw_wide(&k2, r2, &e).unwrap();
// Now add the two signatures together
let s_agg = &s1 + &s2;
// Check that the multi-sig verifies
assert!(s_agg.verify_challenge(&(P1 + P2), &e));
assert!(s_agg.verify_raw_wide(&(P1 + P2), &e));
}

#[test]
Expand Down Expand Up @@ -247,8 +247,8 @@ mod test {
// assert_ne!(sig1, sig2);
// Prove that the nonces were reused. Again, NEVER do this
assert_eq!(sig1.get_public_nonce(), sig2.get_public_nonce());
assert!(sig1.verify_message(&P, msg));
assert!(sig2.verify_message(&P, msg));
assert!(sig1.verify(&P, msg));
assert!(sig2.verify(&P, msg));
// But the signatures are different, for the same message, secret and nonce.
assert_ne!(sig1.get_signature(), sig2.get_signature());
}
Expand All @@ -258,10 +258,9 @@ mod test {
fn sign_and_verify_message() {
let mut rng = rand::thread_rng();
let (k, P) = RistrettoPublicKey::random_keypair(&mut rng);
let sig =
RistrettoSchnorr::sign_message(&k, "Queues are things that happen to other people", &mut rng).unwrap();
assert!(sig.verify_message(&P, "Queues are things that happen to other people"));
assert!(!sig.verify_message(&P, "Qs are things that happen to other people"));
assert!(!sig.verify_message(&(&P + &P), "Queues are things that happen to other people"));
let sig = RistrettoSchnorr::sign(&k, "Queues are things that happen to other people", &mut rng).unwrap();
assert!(sig.verify(&P, "Queues are things that happen to other people"));
assert!(!sig.verify(&P, "Qs are things that happen to other people"));
assert!(!sig.verify(&(&P + &P), "Queues are things that happen to other people"));
}
}
109 changes: 60 additions & 49 deletions src/signatures/schnorr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,40 +73,43 @@ where
P::from_secret_key(&self.signature)
}

/// Sign a challenge with the given `secret` and private `nonce`. Returns an SchnorrSignatureError if `<K as
/// ByteArray>::from_bytes(challenge)` returns an error.
/// Generate a signature using a given secret key, nonce, and challenge byte slice.
///
/// WARNING: The public key and nonce are NOT bound to the challenge. This method assumes that the challenge has
/// been constructed such that all commitments are already included in the challenge.
/// WARNING: This is intended for use cases where the challenge byte slice was generated correctly.
/// In particlar, it _must_ be the result of securely applying a cryptographic hash function to the correct public
/// key, public nonce, and input message; further, it must be of a length suitable for scalar wide reduction.
/// This function only checks that the byte slice is of the correct length.
/// The nonce _must_ also have been sampled uniformly at random and not reused with the same secret key and a
/// different message.
///
/// Use [`sign_raw`] instead if this is what you want. (This method is a deprecated alias for `sign_raw`).
///
/// If you want a simple API that binds the nonce and public key to the message, use [`sign_message`] instead.
#[deprecated(
since = "0.16.0",
note = "This method probably doesn't do what you think it does. Please use `sign_message` or `sign_raw` \
instead, depending on your use case. This function will be removed in v1.0.0"
)]
#[allow(clippy::needless_pass_by_value)]
pub fn sign(secret: K, nonce: K, challenge: &[u8]) -> Result<Self, SchnorrSignatureError>
where
K: Add<Output = K>,
for<'a> K: Mul<&'a K, Output = K>,
{
Self::sign_raw(&secret, nonce, challenge)
/// If you aren't sure that you can meet these requirements, and want a simple and safe API, use [`sign`].
pub fn sign_raw_wide<'a>(secret: &'a K, nonce: K, challenge: &[u8]) -> Result<Self, SchnorrSignatureError>
where K: Add<Output = K> + Mul<&'a K, Output = K> {
// s = r + e.k
let e = match K::from_bytes_wide(challenge) {
Ok(e) => e,
Err(_) => return Err(SchnorrSignatureError::InvalidChallenge),
};
let public_nonce = P::from_secret_key(&nonce);
let ek = e * secret;
let s = ek + nonce;
Ok(Self::new(public_nonce, s))
}

/// Sign a challenge with the given `secret` and private `nonce`. Returns an SchnorrSignatureError if `<K as
/// ByteArray>::from_bytes(challenge)` returns an error.
/// Generate a signature using a given secret key, nonce, and challenge byte slice.
///
/// WARNING: The public key and nonce are NOT bound to the challenge. This method assumes that the challenge has
/// been constructed such that all commitments are already included in the challenge.
/// WARNING: This is intended for use cases where the challenge byte slice was generated correctly.
/// In particlar, it _must_ be the result of securely applying a cryptographic hash function to the correct public
/// key, public nonce, and input message; further, it must be the canonical representation of a scalar.
/// This function only checks that the byte slice is of the correct length.
/// The nonce _must_ also have been sampled uniformly at random and not reused with the same secret key and a
/// different message.
///
/// If you want a simple API that binds the nonce and public key to the message, use [`sign_message`] instead.
pub fn sign_raw<'a>(secret: &'a K, nonce: K, challenge: &[u8]) -> Result<Self, SchnorrSignatureError>
/// If you aren't sure that you can meet these requirements, and want a simple and safe API, use [`sign`].
pub fn sign_raw_canonical<'a>(secret: &'a K, nonce: K, challenge: &[u8]) -> Result<Self, SchnorrSignatureError>
where K: Add<Output = K> + Mul<&'a K, Output = K> {
// s = r + e.k
let e = match K::from_bytes_wide(challenge) {
let e = match K::from_canonical_bytes(challenge) {
Ok(e) => e,
Err(_) => return Err(SchnorrSignatureError::InvalidChallenge),
};
Expand All @@ -119,11 +122,8 @@ where
/// Signs a message with the given secret key.
///
/// This method correctly binds a nonce and the public key to the signature challenge, using domain-separated
/// hashing. The hasher is also opinionated in the sense that Blake2b 256-bit digest is always used.
///
/// it is possible to customise the challenge by using [`construct_domain_separated_challenge`] and [`sign_raw`]
/// yourself, or even use [`sign_raw`] using a completely custom challenge.
pub fn sign_message<'a, B, R: RngCore + CryptoRng>(
/// hashing. The hasher is also opinionated in the sense that Blake2b 512-bit digest is always used.
pub fn sign<'a, B, R: RngCore + CryptoRng>(
secret: &'a K,
message: B,
rng: &mut R,
Expand All @@ -136,16 +136,13 @@ where
Self::sign_with_nonce_and_message(secret, nonce, message)
}

/// Signs a message with the given secret key and provided nonce.
/// Signs a message with the given secret key and nonce.
///
/// This method correctly binds the nonce and the public key to the signature challenge, using domain-separated
/// hashing. The hasher is also opinionated in the sense that Blake2b 256-bit digest is always used.
///
/// ** Important **: It is the caller's responsibility to ensure that the nonce is unique. This API tries to
/// prevent this by taking ownership of the nonce, which means that the caller has to explicitly clone the nonce
/// in order to re-use it, which is a small deterrent, but better than nothing.
/// hashing. The hasher is also opinionated in the sense that Blake2b 512-bit digest is always used.
///
/// To delegate nonce handling to the callee, use [`Self::sign_message`] instead.
/// WARNING: The nonce _must_ also have been sampled uniformly at random and not reused with the same secret key and
/// a different message.
pub fn sign_with_nonce_and_message<'a, B>(
secret: &'a K,
nonce: K,
Expand All @@ -159,7 +156,7 @@ where
let public_key = P::from_secret_key(secret);
let challenge =
Self::construct_domain_separated_challenge::<_, Blake2b<U64>>(&public_nonce, &public_key, message);
Self::sign_raw(secret, nonce, challenge.as_ref())
Self::sign_raw_wide(secret, nonce, challenge.as_ref())
}

/// Constructs an opinionated challenge hash for the given public nonce, public key and message.
Expand All @@ -168,8 +165,8 @@ where
/// the challenge. In this implementation, the challenge is constructed by means of domain separated hashing
/// using the provided digest.
///
/// This challenge is used in the [`sign_message`] and [`verify_message`] methods.If you wish to use a custom
/// challenge, you can use [`sign_raw`] instead.
/// This challenge is used in the [`sign_message`] and [`verify_message`] methods. If you wish to use a custom
/// challenge, you can use [`sign_raw_canonical`] or [`sign_raw_wide`] instead.
pub fn construct_domain_separated_challenge<B, D>(
public_nonce: &P,
public_key: &P,
Expand All @@ -186,23 +183,23 @@ where
.finalize()
}

/// Verifies a signature created by the `sign_message` method. The function returns `true` if and only if the
/// Verifies a signature created by the `sign` method. The function returns `true` if and only if the
/// message was signed by the secret key corresponding to the given public key, and that the challenge was
/// constructed using the domain-separation method defined in [`construct_domain_separated_challenge`].
pub fn verify_message<'a, B>(&self, public_key: &'a P, message: B) -> bool
pub fn verify<'a, B>(&self, public_key: &'a P, message: B) -> bool
where
for<'b> &'b K: Mul<&'a P, Output = P>,
for<'b> &'b P: Add<P, Output = P>,
B: AsRef<[u8]>,
{
let challenge =
Self::construct_domain_separated_challenge::<_, Blake2b<U64>>(&self.public_nonce, public_key, message);
self.verify_challenge(public_key, challenge.as_ref())
self.verify_raw_wide(public_key, challenge.as_ref())
}

/// Returns true if this signature is valid for a public key and challenge, otherwise false. This will always return
/// false if `<K as ByteArray>::from_bytes(challenge)` returns an error.
pub fn verify_challenge<'a>(&self, public_key: &'a P, challenge: &[u8]) -> bool
/// Verifies a signature against a given public key and challenge byte slice.
/// The byte slice is converted to a scalar using wide reduction.
pub fn verify_raw_wide<'a>(&self, public_key: &'a P, challenge: &[u8]) -> bool
where
for<'b> &'b K: Mul<&'a P, Output = P>,
for<'b> &'b P: Add<P, Output = P>,
Expand All @@ -211,11 +208,25 @@ where
Ok(e) => e,
Err(_) => return false,
};
self.verify(public_key, &e)
self.verify_challenge_scalar(public_key, &e)
}

/// Verifies a signature against a given public key and challenge byte slice.
/// The byte slice is converted to a scalar assuming a canonical representation.
pub fn verify_raw_canonical<'a>(&self, public_key: &'a P, challenge: &[u8]) -> bool
where
for<'b> &'b K: Mul<&'a P, Output = P>,
for<'b> &'b P: Add<P, Output = P>,
{
let e = match K::from_canonical_bytes(challenge) {
Ok(e) => e,
Err(_) => return false,
};
self.verify_challenge_scalar(public_key, &e)
}

/// Returns true if this signature is valid for a public key and challenge scalar, otherwise false.
pub fn verify<'a>(&self, public_key: &'a P, challenge: &K) -> bool
pub fn verify_challenge_scalar<'a>(&self, public_key: &'a P, challenge: &K) -> bool
where
for<'b> &'b K: Mul<&'a P, Output = P>,
for<'b> &'b P: Add<P, Output = P>,
Expand Down

0 comments on commit 8dde021

Please sign in to comment.