diff --git a/zebra-chain/src/block/header.rs b/zebra-chain/src/block/header.rs index 9be278622..eb29dcb02 100644 --- a/zebra-chain/src/block/header.rs +++ b/zebra-chain/src/block/header.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Duration, Utc}; -use crate::serialization::ZcashSerialize; use crate::work::{difficulty::CompactDifficulty, equihash::Solution}; use super::{merkle, Error, Hash}; @@ -69,24 +68,6 @@ pub struct Header { } impl Header { - /// Returns true if the header is valid based on its `EquihashSolution` - pub fn is_equihash_solution_valid(&self) -> Result<(), EquihashError> { - let n = 200; - let k = 9; - let nonce = &self.nonce; - let solution = &self.solution.0; - let mut input = Vec::new(); - - self.zcash_serialize(&mut input) - .expect("serialization into a vec can't fail"); - - let input = &input[0..Solution::INPUT_LENGTH]; - - equihash::is_valid_solution(n, k, input, nonce, solution)?; - - Ok(()) - } - /// Check if `self.time` is less than or equal to /// 2 hours in the future, according to the node's local clock (`now`). /// @@ -112,8 +93,3 @@ impl Header { } } } - -#[non_exhaustive] -#[derive(Debug, thiserror::Error)] -#[error("invalid equihash solution for BlockHeader")] -pub struct EquihashError(#[from] equihash::Error); diff --git a/zebra-chain/src/work/equihash.rs b/zebra-chain/src/work/equihash.rs index c92306a0d..e768ee7fd 100644 --- a/zebra-chain/src/work/equihash.rs +++ b/zebra-chain/src/work/equihash.rs @@ -1,11 +1,18 @@ //! Equihash Solution and related items. +use crate::block::Header; use crate::serialization::{ serde_helpers, ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, }; use std::{fmt, io}; +/// The error type for Equihash +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +#[error("invalid equihash solution for BlockHeader")] +pub struct Error(#[from] equihash::Error); + /// The size of an Equihash solution in bytes (always 1344). pub(crate) const SOLUTION_SIZE: usize = 1344; @@ -24,6 +31,25 @@ impl Solution { /// The length of the portion of the header used as input when verifying /// equihash solutions, in bytes pub const INPUT_LENGTH: usize = 4 + 32 * 3 + 4 * 2; + + /// Returns true if the header is valid based on its `EquihashSolution` + pub fn check(&self, block: &Header) -> Result<(), Error> { + let n = 200; + let k = 9; + let nonce = &block.nonce; + let solution = &self.0; + let mut input = Vec::new(); + + block + .zcash_serialize(&mut input) + .expect("serialization into a vec can't fail"); + + let input = &input[0..Solution::INPUT_LENGTH]; + + equihash::is_valid_solution(n, k, input, nonce, solution)?; + + Ok(()) + } } impl PartialEq for Solution { diff --git a/zebra-chain/src/work/tests/prop.rs b/zebra-chain/src/work/tests/prop.rs index 43222afa8..1221a8043 100644 --- a/zebra-chain/src/work/tests/prop.rs +++ b/zebra-chain/src/work/tests/prop.rs @@ -39,11 +39,11 @@ fn equihash_prop_test_solution() -> color_eyre::eyre::Result<()> { for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { let block = Block::zcash_deserialize(&block_bytes[..]) .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; + block.header.solution.check(&block.header)?; proptest!(|(fake_header in randomized_solutions(block.header))| { - fake_header - .is_equihash_solution_valid() + fake_header.solution + .check(&fake_header) .expect_err("block header should not validate on randomized solution"); }); } @@ -71,11 +71,11 @@ fn equihash_prop_test_nonce() -> color_eyre::eyre::Result<()> { for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { let block = Block::zcash_deserialize(&block_bytes[..]) .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; + block.header.solution.check(&block.header)?; proptest!(|(fake_header in randomized_nonce(block.header))| { - fake_header - .is_equihash_solution_valid() + fake_header.solution + .check(&fake_header) .expect_err("block header should not validate on randomized nonce"); }); } @@ -106,11 +106,11 @@ fn equihash_prop_test_input() -> color_eyre::eyre::Result<()> { for block_bytes in zebra_test::vectors::TEST_BLOCKS.iter() { let block = Block::zcash_deserialize(&block_bytes[..]) .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; + block.header.solution.check(&block.header)?; proptest!(|(fake_header in randomized_input(block.header))| { - fake_header - .is_equihash_solution_valid() + fake_header.solution + .check(&fake_header) .expect_err("equihash solution should not validate on randomized input"); }); } diff --git a/zebra-chain/src/work/tests/vectors.rs b/zebra-chain/src/work/tests/vectors.rs index 3b5b8fbc1..9d5a558bd 100644 --- a/zebra-chain/src/work/tests/vectors.rs +++ b/zebra-chain/src/work/tests/vectors.rs @@ -31,7 +31,7 @@ fn equihash_solution_test_vector_is_valid() -> color_eyre::eyre::Result<()> { let block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..]) .expect("block test vector should deserialize"); - block.header.is_equihash_solution_valid()?; + block.header.solution.check(&block.header)?; Ok(()) } diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 992204d76..578152305 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -100,7 +100,7 @@ where if hash > difficulty_threshold { Err("Block failed the difficulty filter: hash must be less than or equal to the difficulty threshold.")?; } - block.header.is_equihash_solution_valid()?; + check::is_equihash_solution_valid(&block.header)?; // Since errors cause an early exit, try to do the // quick checks first. diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 29b55d038..b3c46a3ed 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -1,7 +1,10 @@ //! Consensus check functions use super::*; -use zebra_chain::block::Block; +use zebra_chain::{ + block::{Block, Header}, + work::equihash, +}; /// Check that there is exactly one coinbase transaction in `Block`, and that /// the coinbase transaction is the first transaction in the block. @@ -25,3 +28,7 @@ pub fn is_coinbase_first(block: &Block) -> Result<(), Error> { } Ok(()) } + +pub fn is_equihash_solution_valid(header: &Header) -> Result<(), equihash::Error> { + header.solution.check(&header) +}