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
12pub type Signature = generic_schnorr::Signature<pallas::Point, pallas::Scalar>;
14
15pub struct Message<'m>(pub &'m [u8]);
17
18pub struct Personalization<'p>(pub &'p [u8; 16]);
22
23pub 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
59pub 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}