//! Implement the `v1` protocol's challenge string format //! //! This is a packed byte-string which encodes our puzzle's parameters //! as inputs for Equi-X. We need to construct challenge strings both to //! solve and to verify puzzles. use crate::v1::{Effort, Instance, Nonce, Seed, SolutionError, NONCE_LEN, SEED_LEN}; use arrayvec::{ArrayVec, CapacityError}; use blake2::{digest::consts::U4, Blake2b, Digest}; /// Algorithm personalization string (P) /// /// This becomes part of the challenge string, binding a puzzle solution to /// this particular algorithm even if other similar protocols exist using /// the same building blocks. const P_STRING: &[u8] = b"Tor hs intro v1\0"; /// Length of the personalization string, in bytes const P_STRING_LEN: usize = 16; /// Length of the HsBlindId const ID_LEN: usize = 32; /// Location of the [`Seed`] within a [`Challenge`] const SEED_OFFSET: usize = P_STRING_LEN + ID_LEN; /// Location of the [`Nonce`] within a [`Challenge`] const NONCE_OFFSET: usize = SEED_OFFSET + SEED_LEN; /// Location of the [`Effort`] within a [`Challenge`] const EFFORT_OFFSET: usize = NONCE_OFFSET + NONCE_LEN; /// Packed length of an [`Effort`], in bytes const EFFORT_LEN: usize = 4; /// Total length of our Equi-X challenge string const CHALLENGE_LEN: usize = EFFORT_OFFSET + EFFORT_LEN; /// A fully assembled challenge string, with some access to inner fields /// /// This is the combined input to Equi-X. Defined by Proposal 327 /// as `(P || ID || C || N || INT_32(E))` #[derive(derive_more::AsRef, Debug, Clone, Eq, PartialEq)] pub(super) struct Challenge([u8; CHALLENGE_LEN]); impl Challenge { /// Build a new [`Challenge`]. /// /// Copies [`Instance`], [`Effort`], and [`Nonce`] values into /// a new byte array. pub(super) fn new(instance: &Instance, effort: Effort, nonce: &Nonce) -> Self { let mut result = ArrayVec::::new(); (|| -> Result<(), CapacityError> { result.try_extend_from_slice(P_STRING)?; result.try_extend_from_slice(instance.service().as_ref())?; assert_eq!(result.len(), SEED_OFFSET); result.try_extend_from_slice(instance.seed().as_ref())?; assert_eq!(result.len(), NONCE_OFFSET); result.try_extend_from_slice(nonce.as_ref())?; assert_eq!(result.len(), EFFORT_OFFSET); result.try_extend_from_slice(&effort.as_ref().to_be_bytes()) })() .expect("CHALLENGE_LEN holds a full challenge string"); Self( result .into_inner() .expect("challenge buffer is fully written"), ) } /// Clone the [`Seed`] portion of this challenge. pub(super) fn seed(&self) -> Seed { let array: [u8; SEED_LEN] = self.0[SEED_OFFSET..(SEED_OFFSET + SEED_LEN)] .try_into() .expect("slice length correct"); array.into() } /// Clone the [`Nonce`] portion of this challenge. pub(super) fn nonce(&self) -> Nonce { let array: [u8; NONCE_LEN] = self.0[NONCE_OFFSET..(NONCE_OFFSET + NONCE_LEN)] .try_into() .expect("slice length correct"); array.into() } /// Return the [`Effort`] used in this challenge. pub(super) fn effort(&self) -> Effort { u32::from_be_bytes( self.0[EFFORT_OFFSET..(EFFORT_OFFSET + EFFORT_LEN)] .try_into() .expect("slice length correct"), ) .into() } /// Increment the [`Nonce`] value inside this challenge. /// /// Note that this will take a different amount of time depending on the /// number of bytes affected. There's no timing side channel here: The /// timing variation here is swamped by variation in the solver itself, /// and the nonce is not a secret value. pub(super) fn increment_nonce(&mut self) { /// Wrapping increment for a serialized little endian value of arbitrary width. fn inc_le_bytes(slice: &mut [u8]) { for byte in slice { let (value, overflow) = (*byte).overflowing_add(1); *byte = value; if !overflow { break; } } } inc_le_bytes(&mut self.0[NONCE_OFFSET..(NONCE_OFFSET + NONCE_LEN)]); } /// Verify that a solution proof passes the effort test. /// /// This computes a Blake2b hash of the challenge and the serialized /// Equi-X solution, and tests the result against the effort encoded /// in the challenge string. /// /// Used by both the [`crate::v1::Solver`] and the [`crate::v1::Verifier`]. pub(super) fn check_effort( &self, proof: &equix::SolutionByteArray, ) -> Result<(), SolutionError> { let mut hasher = Blake2b::::new(); hasher.update(self.as_ref()); hasher.update(proof.as_ref()); let value = u32::from_be_bytes(hasher.finalize().into()); match value.checked_mul(*self.effort().as_ref()) { Some(_) => Ok(()), None => Err(SolutionError::Effort), } } }