use std::path::Path;
use anyhow::anyhow;
use crate::StdResult;
#[cfg(feature = "future_snark")]
use crate::crypto_helper::{
BUNDLE_FIRST_HEX_CHAR, GenesisSchnorrSignature, GenesisSchnorrVerificationKey,
GenesisSchnorrVerifier, GenesisVerificationKeyBundle,
};
use crate::crypto_helper::{
GenesisEd25519Signature, GenesisEd25519VerificationKey, GenesisEd25519Verifier, GenesisSigner,
};
pub const LEGACY_FIRST_HEX_CHAR: u8 = b'5';
#[derive(Debug, Clone)]
pub struct GenesisVerifier {
pub ed25519: GenesisEd25519Verifier,
#[cfg(feature = "future_snark")]
pub schnorr: Option<GenesisSchnorrVerifier>,
}
impl GenesisVerifier {
pub fn from_ed25519(verification_key: GenesisEd25519VerificationKey) -> Self {
Self {
ed25519: GenesisEd25519Verifier::from_verification_key(verification_key),
#[cfg(feature = "future_snark")]
schnorr: None,
}
}
pub fn create_deterministic_verifier() -> Self {
GenesisSigner::create_deterministic_signer().create_verifier()
}
#[cfg(feature = "future_snark")]
pub fn from_bundle(bundle: GenesisVerificationKeyBundle) -> Self {
Self {
ed25519: GenesisEd25519Verifier::from_verification_key(bundle.ed25519),
schnorr: Some(GenesisSchnorrVerifier::from_verification_key(
bundle.schnorr,
)),
}
}
pub fn try_from_hex(raw: &str) -> StdResult<Self> {
let trimmed = raw.trim();
match trimmed.as_bytes().first() {
None => Err(anyhow!("genesis verification key input is empty")),
#[cfg(feature = "future_snark")]
Some(&BUNDLE_FIRST_HEX_CHAR) => Ok(Self::from_bundle(
GenesisVerificationKeyBundle::try_from_hex(trimmed)?,
)),
Some(&LEGACY_FIRST_HEX_CHAR) => Ok(Self::from_ed25519(
GenesisEd25519VerificationKey::try_from(trimmed)?,
)),
_ => Err(anyhow!(
"unrecognised genesis verification key format: expected the legacy Ed25519-only file (first hex character `5`) or a dual-signature bundle (first hex character `0`, version 1)"
)),
}
}
pub fn read_from_file(path: &Path) -> StdResult<Self> {
let raw = std::fs::read_to_string(path)?;
Self::try_from_hex(&raw)
}
pub fn verify_ed25519(
&self,
message: &[u8],
signature: &GenesisEd25519Signature,
) -> StdResult<()> {
self.ed25519.verify(message, signature)
}
#[cfg(feature = "future_snark")]
pub fn verify_schnorr(
&self,
sha256_digest: &[u8],
signature: &GenesisSchnorrSignature,
) -> StdResult<()> {
self.schnorr
.as_ref()
.ok_or_else(|| anyhow!("the genesis verifier holds no SNARK verification key"))?
.verify(sha256_digest, signature)
}
pub fn to_ed25519_verification_key(&self) -> GenesisEd25519VerificationKey {
self.ed25519.to_verification_key()
}
#[cfg(feature = "future_snark")]
pub fn to_schnorr_verification_key(&self) -> Option<GenesisSchnorrVerificationKey> {
self.schnorr.as_ref().map(|schnorr| schnorr.to_verification_key())
}
#[cfg(feature = "future_snark")]
pub fn verification_key_bundle(&self) -> Option<GenesisVerificationKeyBundle> {
self.schnorr.as_ref().map(|schnorr| GenesisVerificationKeyBundle {
ed25519: self.ed25519.to_verification_key(),
schnorr: schnorr.to_verification_key(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto_helper::GenesisEd25519Signer;
#[cfg(feature = "future_snark")]
use crate::crypto_helper::GenesisSchnorrSigner;
use crate::temp_dir_create;
fn deterministic_signer() -> GenesisEd25519Signer {
GenesisEd25519Signer::create_deterministic_signer()
}
#[cfg(feature = "future_snark")]
#[test]
fn verify_schnorr_errors_when_verifier_holds_no_schnorr_key() {
let signer = deterministic_signer();
let verifier = GenesisVerifier::from_ed25519(signer.verification_key());
let schnorr_signature = GenesisSchnorrSigner::create_non_deterministic_signer()
.sign_non_deterministic(&[0u8; 32])
.unwrap();
verifier
.verify_schnorr(&[0u8; 32], &schnorr_signature)
.expect_err("verify_schnorr must error when the verifier holds no SNARK key");
}
#[test]
fn from_ed25519_yields_no_schnorr_half() {
let signer = deterministic_signer();
let verifier = GenesisVerifier::from_ed25519(signer.verification_key());
assert_eq!(
verifier.ed25519.to_verification_key().as_bytes(),
signer.verification_key().as_bytes()
);
#[cfg(feature = "future_snark")]
assert!(verifier.schnorr.is_none());
}
#[test]
fn try_from_hex_accepts_legacy_single_ed25519() {
let signer = deterministic_signer();
let legacy_hex = signer.verification_key().to_json_hex().unwrap();
let verifier = GenesisVerifier::try_from_hex(&legacy_hex).unwrap();
assert_eq!(
verifier.ed25519.to_verification_key().as_bytes(),
signer.verification_key().as_bytes()
);
#[cfg(feature = "future_snark")]
assert!(verifier.schnorr.is_none());
}
#[test]
fn try_from_hex_rejects_empty_input() {
GenesisVerifier::try_from_hex(" ").expect_err("empty input must be rejected");
}
#[test]
fn try_from_hex_rejects_unrecognised_format() {
GenesisVerifier::try_from_hex("abc").expect_err("unrecognised format must be rejected");
}
#[test]
fn read_from_file_round_trips_legacy() {
let temp = temp_dir_create!();
let signer = deterministic_signer();
let path = temp.join("genesis.vk");
signer.verification_key().write_json_hex_to_file(&path).unwrap();
let verifier = GenesisVerifier::read_from_file(&path).unwrap();
assert_eq!(
verifier.ed25519.to_verification_key().as_bytes(),
signer.verification_key().as_bytes()
);
}
#[test]
fn verify_ed25519_accepts_valid_signature() {
let signer = deterministic_signer();
let message = b"genesis-message";
let signature = signer.sign(message);
let verifier = GenesisVerifier::from_ed25519(signer.verification_key());
verifier
.verify_ed25519(message, &signature)
.expect("valid signature must verify");
}
#[test]
fn verify_ed25519_rejects_tampered_message() {
let signer = deterministic_signer();
let signature = signer.sign(b"genesis-message");
let verifier = GenesisVerifier::from_ed25519(signer.verification_key());
verifier
.verify_ed25519(b"tampered-message", &signature)
.expect_err("tampered message must be rejected");
}
#[cfg(feature = "future_snark")]
mod schnorr {
use rand_chacha::ChaCha20Rng;
use rand_core::SeedableRng;
use super::*;
fn build_bundle() -> GenesisVerificationKeyBundle {
let ed25519 = deterministic_signer().verification_key();
let mut rng = ChaCha20Rng::from_seed([7u8; 32]);
let schnorr = GenesisSchnorrSigner::generate(&mut rng).verification_key();
GenesisVerificationKeyBundle::new(ed25519, schnorr)
}
#[test]
fn from_bundle_pairs_both_halves() {
let bundle = build_bundle();
let expected_ed25519 = bundle.ed25519.as_bytes().to_vec();
let expected_schnorr = bundle.schnorr.to_bytes();
let verifier = GenesisVerifier::from_bundle(bundle);
assert_eq!(
verifier.ed25519.to_verification_key().as_bytes(),
expected_ed25519.as_slice()
);
assert_eq!(
verifier.schnorr.as_ref().unwrap().to_verification_key().to_bytes(),
expected_schnorr
);
}
#[test]
fn try_from_hex_accepts_dual_bundle() {
let bundle = build_bundle();
let expected_schnorr = bundle.schnorr.to_bytes();
let hex_string = crate::crypto_helper::ProtocolKey::new(bundle).to_bytes_hex().unwrap();
let verifier = GenesisVerifier::try_from_hex(&hex_string).unwrap();
assert_eq!(
verifier.schnorr.as_ref().unwrap().to_verification_key().to_bytes(),
expected_schnorr
);
}
#[test]
fn verify_schnorr_accepts_valid_signature() {
let ed25519 = deterministic_signer().verification_key();
let schnorr_signer = GenesisSchnorrSigner::create_deterministic_signer();
let bundle =
GenesisVerificationKeyBundle::new(ed25519, schnorr_signer.verification_key());
let verifier = GenesisVerifier::from_bundle(bundle);
let digest = [123u8; 32];
let signature = schnorr_signer.sign_non_deterministic(&digest).unwrap();
verifier
.verify_schnorr(&digest, &signature)
.expect("a valid SNARK signature must verify through the wrapper");
}
#[test]
fn verification_key_bundle_mirrors_verifier_halves() {
let verifier = GenesisVerifier::from_bundle(build_bundle());
let expected_schnorr =
verifier.schnorr.as_ref().unwrap().to_verification_key().to_bytes();
let bundle = verifier.verification_key_bundle().unwrap();
assert_eq!(
bundle.ed25519.as_bytes(),
verifier.ed25519.to_verification_key().as_bytes()
);
assert_eq!(bundle.schnorr.to_bytes(), expected_schnorr);
}
#[test]
fn to_schnorr_verification_key_returns_the_schnorr_half_for_a_dual_verifier() {
let bundle = build_bundle();
let expected_schnorr = bundle.schnorr.to_bytes();
let verifier = GenesisVerifier::from_bundle(bundle);
assert_eq!(
verifier.to_schnorr_verification_key().unwrap().to_bytes(),
expected_schnorr
);
}
#[test]
fn to_schnorr_verification_key_is_none_for_a_legacy_verifier() {
let verifier = GenesisVerifier::from_ed25519(deterministic_signer().verification_key());
assert!(verifier.to_schnorr_verification_key().is_none());
}
}
}