Skip to main content

schnorr_orchard/
lib.rs

1use group::GroupEncoding;
2use group::ff::FromUniformBytes;
3use group::ff::PrimeField;
4use pasta_curves::arithmetic::CurveExt;
5use pasta_curves::pallas;
6use rand_core::{CryptoRng, RngCore};
7
8pub use orchard::Address;
9pub use orchard::keys::Diversifier;
10pub use orchard::keys::IncomingViewingKey;
11
12/// Signature produced with [`sign`].
13pub type Signature = generic_schnorr::Signature<pallas::Point, pallas::Scalar>;
14
15/// Message to be signed.
16pub struct Message<'m>(pub &'m [u8]);
17
18/// Personalization string of the signing scheme.
19///
20/// Useful for domain separation.
21pub struct Personalization<'p>(pub &'p [u8; 16]);
22
23/// Produce a [`Signature`] over `message`.
24///
25/// ## Safety
26///
27/// The user must not provide a random number generator
28/// instance that is likely to produce nonce values that
29/// have already been instantiated for previous signatures.
30/// This poses the risk of exposing the underlying [`IncomingViewingKey`].
31/// The consequence of this is it exposes every note associated
32/// with that key.
33pub fn sign<Rng>(
34    Personalization(personalization): Personalization<'_>,
35    Message(message): Message<'_>,
36    key: &IncomingViewingKey,
37    diversifier: &Diversifier,
38    rng: Rng,
39) -> Signature
40where
41    Rng: RngCore + CryptoRng,
42{
43    let secret_key = pallas::Scalar::from_repr({
44        let mut buf = [0u8; 32];
45        buf.copy_from_slice(&key.to_bytes()[32..]);
46        buf
47    })
48    .unwrap();
49
50    generic_schnorr::sign(
51        &generator_from_div(diversifier.as_array()),
52        message,
53        &secret_key,
54        rng,
55        |nonce, public_key, message| hash_to_field(personalization, nonce, public_key, message),
56    )
57}
58
59/// Verify a [`Signature`] produced with an [`IncomingViewingKey`],
60/// using its associated [`Address`].
61pub fn verify(
62    Personalization(personalization): Personalization<'_>,
63    Message(message): Message<'_>,
64    addr: &Address,
65    signature: &Signature,
66) -> bool {
67    let addr = addr.to_raw_address_bytes();
68
69    let public_key = pallas::Point::from_bytes(&{
70        let mut buf = [0u8; 32];
71        buf.copy_from_slice(&addr[11..]);
72        buf
73    })
74    .unwrap();
75
76    generic_schnorr::verify(
77        &generator_from_div(&addr[..11]),
78        message,
79        &public_key,
80        signature,
81        |nonce, public_key, message| hash_to_field(personalization, nonce, public_key, message),
82    )
83}
84
85fn generator_from_div(diversifier: &[u8]) -> pallas::Point {
86    const KEY_DIVERSIFICATION_PERSONALIZATION: &str = "z.cash:Orchard-gd";
87
88    let hasher = pallas::Point::hash_to_curve(KEY_DIVERSIFICATION_PERSONALIZATION);
89    hasher(diversifier)
90}
91
92fn hash_to_field(
93    personalization: &[u8; 16],
94    nonce: &pallas::Point,
95    public_key: &pallas::Point,
96    message: &[u8],
97) -> pallas::Scalar {
98    let hash = blake2b_simd::Params::new()
99        .hash_length(64)
100        .personal(&personalization[..])
101        .to_state()
102        .update(&nonce.to_bytes())
103        .update(&public_key.to_bytes())
104        .update(message)
105        .finalize();
106
107    pallas::Scalar::from_uniform_bytes(hash.as_array())
108}
109
110#[cfg(test)]
111mod tests {
112    use orchard::keys::{FullViewingKey, Scope, SpendingKey};
113    use rand_chacha::ChaCha20Rng;
114    use rand_core::SeedableRng;
115
116    use super::*;
117
118    #[test]
119    fn test_sign_verify() {
120        let sk = SpendingKey::from_bytes([7; 32]).unwrap();
121        let fvk = FullViewingKey::from(&sk);
122        let address = fvk.address_at(0u32, Scope::External);
123
124        let signature = sign(
125            Personalization(&[0u8; 16]),
126            Message(b"bepis"),
127            &fvk.to_ivk(Scope::External),
128            &address.diversifier(),
129            test_csprng(),
130        );
131
132        assert!(verify(
133            Personalization(&[0u8; 16]),
134            Message(b"bepis"),
135            &address,
136            &signature,
137        ));
138    }
139
140    fn test_csprng() -> ChaCha20Rng {
141        ChaCha20Rng::from_seed([0xbe; 32])
142    }
143}