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