schnorr-orchard 0.3.0

Schnorr signatures using Orchard payment addresses
Documentation
use group::GroupEncoding;
use group::ff::FromUniformBytes;
use group::ff::PrimeField;
use pasta_curves::arithmetic::CurveExt;
use pasta_curves::pallas;
use rand_core::{CryptoRng, RngCore};

pub use orchard::Address;
pub use orchard::keys::Diversifier;
pub use orchard::keys::IncomingViewingKey;

/// Signature produced with [`sign`].
pub type Signature = generic_schnorr::Signature<pallas::Point, pallas::Scalar>;

/// Message to be signed.
pub struct Message<'m>(pub &'m [u8]);

/// Personalization string of the signing scheme.
///
/// Useful for domain separation.
pub struct Personalization<'p>(pub &'p [u8; 16]);

/// Produce a [`Signature`] over `message`.
///
/// ## Safety
///
/// The user must not provide a random number generator
/// instance that is likely to produce nonce values that
/// have already been instantiated for previous signatures.
/// This poses the risk of exposing the underlying [`IncomingViewingKey`].
/// The consequence of this is it exposes every note associated
/// with that key.
pub fn sign<Rng>(
    Personalization(personalization): Personalization<'_>,
    Message(message): Message<'_>,
    key: &IncomingViewingKey,
    diversifier: &Diversifier,
    rng: Rng,
) -> Signature
where
    Rng: RngCore + CryptoRng,
{
    let secret_key = pallas::Scalar::from_repr({
        let mut buf = [0u8; 32];
        buf.copy_from_slice(&key.to_bytes()[32..]);
        buf
    })
    .unwrap();

    generic_schnorr::sign(
        &generator_from_div(diversifier.as_array()),
        message,
        &secret_key,
        rng,
        |nonce, public_key, message| hash_to_field(personalization, nonce, public_key, message),
    )
}

/// Verify a [`Signature`] produced with an [`IncomingViewingKey`],
/// using its associated [`Address`].
pub fn verify(
    Personalization(personalization): Personalization<'_>,
    Message(message): Message<'_>,
    addr: &Address,
    signature: &Signature,
) -> bool {
    let addr = addr.to_raw_address_bytes();

    let public_key = pallas::Point::from_bytes(&{
        let mut buf = [0u8; 32];
        buf.copy_from_slice(&addr[11..]);
        buf
    })
    .unwrap();

    generic_schnorr::verify(
        &generator_from_div(&addr[..11]),
        message,
        &public_key,
        signature,
        |nonce, public_key, message| hash_to_field(personalization, nonce, public_key, message),
    )
}

fn generator_from_div(diversifier: &[u8]) -> pallas::Point {
    const KEY_DIVERSIFICATION_PERSONALIZATION: &str = "z.cash:Orchard-gd";

    let hasher = pallas::Point::hash_to_curve(KEY_DIVERSIFICATION_PERSONALIZATION);
    hasher(diversifier)
}

fn hash_to_field(
    personalization: &[u8; 16],
    nonce: &pallas::Point,
    public_key: &pallas::Point,
    message: &[u8],
) -> pallas::Scalar {
    let hash = blake2b_simd::Params::new()
        .hash_length(64)
        .personal(&personalization[..])
        .to_state()
        .update(&nonce.to_bytes())
        .update(&public_key.to_bytes())
        .update(message)
        .finalize();

    pallas::Scalar::from_uniform_bytes(hash.as_array())
}

#[cfg(test)]
mod tests {
    use orchard::keys::{FullViewingKey, Scope, SpendingKey};
    use rand_chacha::ChaCha20Rng;
    use rand_core::SeedableRng;

    use super::*;

    #[test]
    fn test_sign_verify() {
        let sk = SpendingKey::from_bytes([7; 32]).unwrap();
        let fvk = FullViewingKey::from(&sk);
        let address = fvk.address_at(0u32, Scope::External);

        let signature = sign(
            Personalization(&[0u8; 16]),
            Message(b"bepis"),
            &fvk.to_ivk(Scope::External),
            &address.diversifier(),
            test_csprng(),
        );

        assert!(verify(
            Personalization(&[0u8; 16]),
            Message(b"bepis"),
            &address,
            &signature,
        ));
    }

    fn test_csprng() -> ChaCha20Rng {
        ChaCha20Rng::from_seed([0xbe; 32])
    }
}