verilot 0.1.0

Command line tool for running and checking verifiable lotteries.
use blake2::{Blake2b, Digest};
use generic_array::GenericArray;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{
    error::Error,
    fmt,
    fs::File,
    io::{BufRead, Read, Write},
    path::PathBuf,
};
use structopt::StructOpt;

#[derive(StructOpt)]
struct Options {
    #[structopt(subcommand)]
    command: Command,
}

#[derive(StructOpt)]
enum Command {
    Generate {
        secret_out: PathBuf,
    },
    Lottery {
        #[structopt(long)]
        secret: PathBuf,
    },
    Verify {
        #[structopt(long)]
        commitment: Option<String>,
    },
    Digest,
}

type DynResult<T> = Result<T, Box<dyn Error>>;

fn main() -> DynResult<()> {
    let options = Options::from_args();

    let stdin = std::io::stdin();
    let mut stdout = std::io::stdout();

    match options.command {
        Command::Generate { secret_out } => {
            let mut out = File::create(&secret_out)?;
            do_generate(&mut out, &mut stdout)
        }
        Command::Lottery { secret } => {
            let secret = std::fs::read(&secret)?;
            do_lottery(&secret, stdin.lock(), &mut stdout)
        }
        Command::Verify { commitment } => do_verify(stdin, commitment.as_ref()),
        Command::Digest => {
            for line in stdin.lock().lines() {
                let line = line?;
                let hash = Hash::of(&[line.as_bytes()]);
                writeln!(stdout, "{} \"{}\"", hash.to_string(), line)?;
            }
            Ok(())
        }
    }
}

fn do_generate(secret_out: &mut impl Write, stdout: &mut impl Write) -> DynResult<()> {
    use rand::RngCore;

    let mut rng = rand::thread_rng();
    let mut random_bytes = [0; 64];
    rng.fill_bytes(&mut random_bytes);

    let hex_bytes = hex::encode(&random_bytes);
    secret_out.write(hex_bytes.as_bytes())?;

    let commitment = Hash::of(&[&random_bytes]);
    writeln!(stdout, "{}", commitment.to_string())?;

    Ok(())
}

fn do_lottery(secret: &[u8], stdin: impl BufRead, stdout: &mut impl Write) -> DynResult<()> {
    let entries = stdin.lines().collect::<Result<Vec<_>, _>>()?;

    let outcome = Outcome::generate(entries, hex::decode(secret)?);
    serde_json::to_writer_pretty(stdout, &outcome)?;

    Ok(())
}

fn do_verify(stdin: impl Read, commitment: Option<&String>) -> DynResult<()> {
    let outcome: Outcome = serde_json::from_reader(stdin)?;

    outcome.verify();

    if let Some(commitment) = commitment {
        let hash = Hash::parse(commitment)?;
        outcome.verify_commitment(hash);
    }

    Ok(())
}

#[derive(PartialOrd, Ord, PartialEq, Eq, Debug)]
struct Hash(GenericArray<u8, <Blake2b as Digest>::OutputSize>);

impl Hash {
    fn of(sequences: &[&[u8]]) -> Self {
        let mut hasher = Blake2b::new();
        for bytes in sequences {
            hasher.update(bytes);
        }
        Hash(hasher.finalize())
    }

    fn parse(s: &str) -> DynResult<Self> {
        Ok(serde_json::from_str(&format!("\"{}\"", s))?)
    }
}

impl Serialize for Hash {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex::encode(self.0))
    }
}

impl<'de> Deserialize<'de> for Hash {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct StringVisitor;

        impl<'de> serde::de::Visitor<'de> for StringVisitor {
            type Value = Hash;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a 64 byte hex string")
            }

            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                let bytes = hex::decode(s).map_err(E::custom)?;
                let array =
                    GenericArray::from_exact_iter(bytes).ok_or(E::custom("incorrect length"))?;
                Ok(Hash(array))
            }
        }

        deserializer.deserialize_str(StringVisitor)
    }
}

impl ToString for Hash {
    fn to_string(&self) -> String {
        hex::encode(self.0)
    }
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Outcome {
    ordered_entries: Vec<Entry>,
    secret: Vec<u8>,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Entry {
    entrant: String,
    ticket: Hash,
}

impl PartialOrd for Entry {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.ticket.partial_cmp(&other.ticket)
    }
}

impl Ord for Entry {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.ticket.cmp(&other.ticket)
    }
}

impl Outcome {
    fn generate(entrants: Vec<String>, secret: Vec<u8>) -> Outcome {
        let mut hashed_entries = entrants
            .into_iter()
            .map(|entrant| Entry {
                ticket: Hash::of(&[&entrant.as_bytes(), &secret]),
                entrant,
            })
            .collect::<Vec<_>>();
        hashed_entries.sort();
        Outcome {
            ordered_entries: hashed_entries,
            secret,
        }
    }

    fn verify(&self) {
        let entries = self
            .ordered_entries
            .iter()
            .map(|e| &e.entrant)
            .cloned()
            .collect();
        let generated = Outcome::generate(entries, self.secret.clone());
        assert_eq!(self, &generated);
    }

    fn verify_commitment(&self, hash: Hash) {
        let expected = Hash::of(&[&self.secret]);
        assert_eq!(hash, expected);
    }
}