use core::{
cmp::Ordering,
hash::{Hash, Hasher},
marker::PhantomData,
ops::{Add, Mul},
};
use blake2::Blake2b;
use digest::{Digest, consts::U64};
use rand_core::{CryptoRng, Rng};
use snafu::prelude::*;
use tari_utilities::ByteArray;
use crate::{
hash_domain,
hashing::{DomainSeparatedHash, DomainSeparatedHasher, DomainSeparation},
keys::{PublicKey, SecretKey},
};
hash_domain!(SchnorrSigChallenge, "com.tari.schnorr_signature", 1);
#[derive(Clone, Debug, Snafu, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)]
pub enum SchnorrSignatureError {
#[snafu(display("An invalid challenge was provided"))]
InvalidChallenge,
}
#[allow(non_snake_case)]
#[derive(Copy, Debug, Clone)]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SchnorrSignature<P, K, H = SchnorrSigChallenge> {
pub(crate) public_nonce: P,
pub(crate) signature: K,
#[cfg_attr(feature = "serde", serde(skip))]
_phantom: PhantomData<H>,
}
impl<P, K, H> SchnorrSignature<P, K, H>
where
P: PublicKey<K = K>,
K: SecretKey,
H: DomainSeparation,
{
pub fn new(public_nonce: P, signature: K) -> Self {
SchnorrSignature {
public_nonce,
signature,
_phantom: PhantomData,
}
}
fn calc_signature_verifier(&self) -> P {
P::from_secret_key(&self.signature)
}
pub fn sign_raw_uniform<'a>(secret: &'a K, nonce: K, challenge: &[u8]) -> Result<Self, SchnorrSignatureError>
where K: Add<Output = K> + Mul<&'a K, Output = K> {
let e = match K::from_uniform_bytes(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))
}
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> {
let e = match K::from_canonical_bytes(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))
}
pub fn sign<'a, B, R: Rng + CryptoRng>(
secret: &'a K,
message: B,
rng: &mut R,
) -> Result<Self, SchnorrSignatureError>
where
K: Add<Output = K> + Mul<&'a K, Output = K>,
B: AsRef<[u8]>,
{
let nonce = K::random(rng);
Self::sign_with_nonce_and_message(secret, nonce, message)
}
pub fn sign_with_nonce_and_message<'a, B>(
secret: &'a K,
nonce: K,
message: B,
) -> Result<Self, SchnorrSignatureError>
where
K: Add<Output = K> + Mul<&'a K, Output = K>,
B: AsRef<[u8]>,
{
let public_nonce = P::from_secret_key(&nonce);
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_uniform(secret, nonce, challenge.as_ref())
}
pub fn construct_domain_separated_challenge<B, D>(
public_nonce: &P,
public_key: &P,
message: B,
) -> DomainSeparatedHash<D>
where
B: AsRef<[u8]>,
D: Digest,
{
DomainSeparatedHasher::<D, H>::new_with_label("challenge")
.chain(public_nonce.as_bytes())
.chain(public_key.as_bytes())
.chain(message.as_ref())
.finalize()
}
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_raw_uniform(public_key, challenge.as_ref())
}
pub fn verify_raw_uniform<'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_uniform_bytes(challenge) {
Ok(e) => e,
Err(_) => return false,
};
self.verify_challenge_scalar(public_key, &e)
}
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)
}
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>,
{
if public_key == &P::default() {
return false;
}
let lhs = self.calc_signature_verifier();
let rhs = &self.public_nonce + challenge * public_key;
lhs == rhs
}
pub fn get_signature(&self) -> &K {
&self.signature
}
pub fn get_public_nonce(&self) -> &P {
&self.public_nonce
}
}
impl<'a, 'b, P, K, H> Add<&'b SchnorrSignature<P, K>> for &'a SchnorrSignature<P, K, H>
where
P: PublicKey<K = K>,
&'a P: Add<&'b P, Output = P>,
K: SecretKey,
&'a K: Add<&'b K, Output = K>,
H: DomainSeparation,
{
type Output = SchnorrSignature<P, K>;
fn add(self, rhs: &'b SchnorrSignature<P, K>) -> SchnorrSignature<P, K> {
let r_sum = self.get_public_nonce() + rhs.get_public_nonce();
let s_sum = self.get_signature() + rhs.get_signature();
SchnorrSignature::new(r_sum, s_sum)
}
}
impl<'a, P, K, H> Add<SchnorrSignature<P, K>> for &'a SchnorrSignature<P, K, H>
where
P: PublicKey<K = K>,
for<'b> &'a P: Add<&'b P, Output = P>,
K: SecretKey,
for<'b> &'a K: Add<&'b K, Output = K>,
H: DomainSeparation,
{
type Output = SchnorrSignature<P, K>;
fn add(self, rhs: SchnorrSignature<P, K>) -> SchnorrSignature<P, K> {
let r_sum = self.get_public_nonce() + rhs.get_public_nonce();
let s_sum = self.get_signature() + rhs.get_signature();
SchnorrSignature::new(r_sum, s_sum)
}
}
impl<P, K, H> Default for SchnorrSignature<P, K, H>
where
P: PublicKey<K = K>,
K: SecretKey,
H: DomainSeparation,
{
fn default() -> Self {
SchnorrSignature::new(P::default(), K::default())
}
}
impl<P, K, H> Ord for SchnorrSignature<P, K, H>
where
P: Eq + Ord,
K: Eq + ByteArray,
{
fn cmp(&self, other: &Self) -> Ordering {
match self.public_nonce.cmp(&other.public_nonce) {
Ordering::Equal => self.signature.as_bytes().cmp(other.signature.as_bytes()),
v => v,
}
}
}
impl<P, K, H> PartialOrd for SchnorrSignature<P, K, H>
where
P: Eq + Ord,
K: Eq + ByteArray,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<P, K, H> Eq for SchnorrSignature<P, K, H>
where
P: Eq,
K: Eq,
{
}
impl<P, K, H> PartialEq for SchnorrSignature<P, K, H>
where
P: PartialEq,
K: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.public_nonce.eq(&other.public_nonce) && self.signature.eq(&other.signature)
}
}
impl<P, K, H> Hash for SchnorrSignature<P, K, H>
where
P: Hash,
K: Hash,
{
fn hash<T: Hasher>(&self, state: &mut T) {
self.public_nonce.hash(state);
self.signature.hash(state);
}
}
#[cfg(test)]
mod test {
use crate::{hashing::DomainSeparation, signatures::SchnorrSigChallenge};
#[test]
fn schnorr_hash_domain() {
assert_eq!(SchnorrSigChallenge::domain(), "com.tari.schnorr_signature");
assert_eq!(
SchnorrSigChallenge::domain_separation_tag("test"),
"com.tari.schnorr_signature.v1.test"
);
}
}