From 09c4e3671daa9df29113186ea098a6639f234007 Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Wed, 2 Oct 2024 15:35:31 +0200 Subject: [PATCH 1/3] Comprehensive parsing & validation --- world-chain-builder/src/pbh/semaphore.rs | 188 +++++++++++++++++++++- world-chain-builder/src/pool/error.rs | 12 +- world-chain-builder/src/pool/validator.rs | 176 +++++++++++--------- 3 files changed, 296 insertions(+), 80 deletions(-) diff --git a/world-chain-builder/src/pbh/semaphore.rs b/world-chain-builder/src/pbh/semaphore.rs index 8b9af008..db13c57a 100644 --- a/world-chain-builder/src/pbh/semaphore.rs +++ b/world-chain-builder/src/pbh/semaphore.rs @@ -1,7 +1,15 @@ +use std::str::FromStr; + use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable}; +use chrono::{Datelike, NaiveDate}; use semaphore::packed_proof::PackedProof; use semaphore::Field; use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use super::tx::Prefix; + +pub const TREE_DEPTH: usize = 30; const LEN: usize = 256; @@ -50,11 +58,157 @@ pub struct SemaphoreProof { pub proof: Proof, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ExternalNullifier { + pub month: MonthMarker, + pub nonce: u16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MonthMarker { + pub year: i32, + pub month: u32, +} + +impl ExternalNullifier { + pub fn new(month: MonthMarker, nonce: u16) -> Self { + Self { month, nonce } + } +} + +impl std::fmt::Display for ExternalNullifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}-{}", Prefix::V1, self.month, self.nonce) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum ExternalNullifierParsingError { + #[error("invalid format - expected a string of format `vv-mmyyyy-xxx...` got {actual}")] + InvaldFormat { actual: String }, + + #[error("error parsing prefix - {0}")] + InvalidPrefix(strum::ParseError), + + #[error("error parsing month - {0}")] + InvalidMonth(MonthMarkerParsingError), + + #[error("error parsing nonce - {0}")] + InvalidNonce(std::num::ParseIntError), + + #[error("leading zeroes in nonce `{0}`")] + LeadingZeroes(String), +} + +impl FromStr for ExternalNullifier { + type Err = ExternalNullifierParsingError; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split('-').collect(); + if parts.len() != 3 { + return Err(ExternalNullifierParsingError::InvaldFormat { + actual: s.to_string(), + }); + } + + // no need to check the exact value since there's only one variant + let Prefix::V1 = parts[0] + .parse() + .map_err(ExternalNullifierParsingError::InvalidPrefix)?; + + let month = parts[1] + .parse() + .map_err(ExternalNullifierParsingError::InvalidMonth)?; + + let nonce_str = parts[2]; + let nonce_str_trimmed = nonce_str.trim_start_matches('0'); + + if nonce_str != "0" && nonce_str != nonce_str_trimmed { + return Err(ExternalNullifierParsingError::LeadingZeroes( + nonce_str.to_string(), + )); + } + + let nonce = nonce_str + .parse() + .map_err(ExternalNullifierParsingError::InvalidNonce)?; + + Ok(ExternalNullifier { month, nonce }) + } +} + +impl MonthMarker { + pub fn new(year: i32, month: u32) -> Self { + Self { year, month } + } +} + +impl From for MonthMarker +where + T: Datelike, +{ + fn from(value: T) -> Self { + Self { + year: value.year(), + month: value.month(), + } + } +} + +impl From for NaiveDate { + fn from(value: MonthMarker) -> Self { + NaiveDate::from_ymd_opt(value.year, value.month, 1).unwrap() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum MonthMarkerParsingError { + #[error("invalid length - expected 6 characters got {actual}")] + InvaldLength { actual: usize }, + #[error("error parsing month - {0}")] + InvalidMonth(std::num::ParseIntError), + #[error("month out of range - expected 01-12 got {month}")] + MonthOutOfRange { month: u32 }, + #[error("error parsing year - {0}")] + InvalidYear(std::num::ParseIntError), +} + +impl FromStr for MonthMarker { + type Err = MonthMarkerParsingError; + + fn from_str(s: &str) -> Result { + if s.len() != 6 { + return Err(MonthMarkerParsingError::InvaldLength { actual: s.len() }); + } + + let month = &s[..2]; + let year = &s[2..]; + + let month = month + .parse() + .map_err(MonthMarkerParsingError::InvalidMonth)?; + let year = year.parse().map_err(MonthMarkerParsingError::InvalidYear)?; + + if month < 1 || month > 12 { + return Err(MonthMarkerParsingError::MonthOutOfRange { month }); + } + + Ok(MonthMarker { year, month }) + } +} + +impl std::fmt::Display for MonthMarker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:02}{:04}", self.month, self.year) + } +} + #[cfg(test)] mod test { + use ethers_core::types::U256; + use test_case::test_case; use super::*; - use ethers_core::types::U256; #[test] fn encode_decode() { @@ -79,4 +233,36 @@ mod test { let decoded = SemaphoreProof::decode(&mut buf).unwrap(); assert_eq!(semaphore_proof, decoded); } + + #[test_case("v1-012025-11")] + #[test_case("v1-012025-19")] + fn parse_external_nulliifer_roundtrip(s: &str) { + let e: ExternalNullifier = s.parse().unwrap(); + + assert_eq!(e.to_string(), s); + } + + #[test_case("v2-012025-11")] + #[test_case("v1-012025-011")] + fn parse_external_nulliifer_invalid(s: &str) { + s.parse::().unwrap_err(); + } + + #[test_case("012024")] + #[test_case("022024")] + #[test_case("022025")] + fn parse_month_marker_roundtrip(s: &str) { + let m: MonthMarker = s.parse().unwrap(); + + assert_eq!(m.to_string(), s); + } + + #[test_case("132024" ; "invalid month")] + #[test_case("12024" ; "too short")] + #[test_case("003024" ; "zero month")] + #[test_case("" ; "empty")] + #[test_case("23012024" ; "too long")] + fn parse_month_marker_invalid(s: &str) { + s.parse::().unwrap_err(); + } } diff --git a/world-chain-builder/src/pool/error.rs b/world-chain-builder/src/pool/error.rs index 62091c4b..e0e875e9 100644 --- a/world-chain-builder/src/pool/error.rs +++ b/world-chain-builder/src/pool/error.rs @@ -1,15 +1,19 @@ -use reth_provider::ProviderError; - use reth_db::{DatabaseError, DatabaseWriteOperation}; +use reth_provider::ProviderError; use reth_transaction_pool::error::{InvalidPoolTransactionError, PoolTransactionError}; use reth_transaction_pool::{PoolTransaction, TransactionValidationOutcome}; +use semaphore::Field; + +use crate::pbh::semaphore::ExternalNullifierParsingError; #[derive(Debug, thiserror::Error)] pub enum WorldChainTransactionPoolInvalid { #[error("nullifier has already been seen")] NullifierAlreadyExists, - #[error("invalid external nullifier")] - InvalidExternalNullifier, + #[error("invalid external nullifier - {0}")] + InvalidExternalNullifier(ExternalNullifierParsingError), + #[error("invalid external nullifier hash - expected {expected:?} got {actual:?}")] + InvalidExternalNullifierHash { expected: Field, actual: Field }, #[error("invalid external nullifier prefix")] InvalidExternalNullifierPrefix, #[error("invalid external nullifier period")] diff --git a/world-chain-builder/src/pool/validator.rs b/world-chain-builder/src/pool/validator.rs index 6bac6577..b37a1763 100644 --- a/world-chain-builder/src/pool/validator.rs +++ b/world-chain-builder/src/pool/validator.rs @@ -1,12 +1,8 @@ //! World Chain transaction pool types -use chrono::{DateTime, Datelike}; -use reth_db::cursor::DbCursorRW; -use reth_db::transaction::{DbTx, DbTxMut}; -use semaphore::hash_to_field; -use semaphore::protocol::verify_proof; -use std::str::FromStr as _; use std::sync::Arc; +use reth_db::cursor::DbCursorRW; +use reth_db::transaction::{DbTx, DbTxMut}; use reth_db::{Database, DatabaseEnv, DatabaseError}; use reth_node_optimism::txpool::OpTransactionValidator; use reth_primitives::{SealedBlock, TxHash}; @@ -15,15 +11,15 @@ use reth_transaction_pool::{ Pool, TransactionOrigin, TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator, }; - -use crate::pbh::db::{ExecutedPbhNullifierTable, ValidatedPbhTransactionTable}; -use crate::pbh::semaphore::SemaphoreProof; -use crate::pbh::tx::Prefix; +use semaphore::hash_to_field; +use semaphore::protocol::verify_proof; use super::error::{TransactionValidationError, WorldChainTransactionPoolInvalid}; use super::ordering::WorldChainOrdering; use super::root::WorldChainRootValidator; use super::tx::{WorldChainPoolTransaction, WorldChainPooledTransaction}; +use crate::pbh::db::{ExecutedPbhNullifierTable, ValidatedPbhTransactionTable}; +use crate::pbh::semaphore::{ExternalNullifier, MonthMarker, SemaphoreProof, TREE_DEPTH}; /// Type alias for World Chain transaction pool pub type WorldChainTransactionPool = Pool< @@ -100,31 +96,32 @@ where pub fn validate_external_nullifier( &self, date: chrono::DateTime, - external_nullifier: &str, + semaphore_proof: &SemaphoreProof, ) -> Result<(), TransactionValidationError> { - let split = external_nullifier.split('-').collect::>(); - - if split.len() != 3 { - return Err(WorldChainTransactionPoolInvalid::InvalidExternalNullifier.into()); - } - - // TODO: Figure out what we actually want to do with the prefix - // For now, we just check that it's a valid prefix - // Maybe in future use as some sort of versioning? - if Prefix::from_str(split[0]).is_err() { - return Err(WorldChainTransactionPoolInvalid::InvalidExternalNullifierPrefix.into()); + let external_nullifier: ExternalNullifier = semaphore_proof + .external_nullifier + .parse() + .map_err(WorldChainTransactionPoolInvalid::InvalidExternalNullifier) + .map_err(TransactionValidationError::from)?; + + let current_date = MonthMarker::from(date); + if external_nullifier.month != current_date { + return Err(WorldChainTransactionPoolInvalid::InvalidExternalNullifierPeriod.into()); } - // TODO: Handle edge case where we are at the end of the month - if split[1] != format_date(date) { - return Err(WorldChainTransactionPoolInvalid::InvalidExternalNullifierPeriod.into()); + if external_nullifier.nonce >= self.num_pbh_txs { + return Err(WorldChainTransactionPoolInvalid::InvalidExternalNullifierNonce.into()); } - match split[2].parse::() { - Ok(nonce) if nonce < self.num_pbh_txs => {} - _ => { - return Err(WorldChainTransactionPoolInvalid::InvalidExternalNullifierNonce.into()); - } + let external_nullifier_hash = hash_to_field(&semaphore_proof.external_nullifier.as_bytes()); + if external_nullifier_hash != semaphore_proof.external_nullifier_hash { + return Err( + WorldChainTransactionPoolInvalid::InvalidExternalNullifierHash { + expected: external_nullifier_hash, + actual: semaphore_proof.external_nullifier_hash, + } + .into(), + ); } Ok(()) @@ -178,7 +175,7 @@ where let date = chrono::Utc::now(); self.validate_root(semaphore_proof)?; - self.validate_external_nullifier(date, &semaphore_proof.external_nullifier)?; + self.validate_external_nullifier(date, &semaphore_proof)?; self.validate_nullifier(semaphore_proof)?; self.validate_signal_hash(transaction.hash(), semaphore_proof)?; @@ -188,7 +185,7 @@ where semaphore_proof.signal_hash, semaphore_proof.external_nullifier_hash, &semaphore_proof.proof.0, - 30, + TREE_DEPTH, ); match res { @@ -261,10 +258,6 @@ where } } -fn format_date(date: DateTime) -> String { - format!("{:0>2}{}", date.month(), date.year()) -} - #[cfg(test)] mod tests { use alloy_primitives::TxKind; @@ -277,31 +270,29 @@ mod tests { TransactionSigned, TransactionSignedEcRecovered, TxDeposit, }; use reth_provider::test_utils::{ExtendedAccount, MockEthProvider}; + use reth_transaction_pool::blobstore::InMemoryBlobStore; + use reth_transaction_pool::validate::EthTransactionValidatorBuilder; use reth_transaction_pool::{ - blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder, - EthPooledTransaction, TransactionOrigin, TransactionValidationOutcome, + EthPooledTransaction, Pool, PoolTransaction as _, TransactionOrigin, TransactionPool, + TransactionValidationOutcome, TransactionValidator, }; - use reth_transaction_pool::{ - Pool, PoolTransaction as _, TransactionPool, TransactionValidator, - }; - use revm_primitives::hex; use semaphore::identity::Identity; use semaphore::poseidon_tree::LazyPoseidonTree; use semaphore::protocol::{generate_nullifier_hash, generate_proof}; use semaphore::{hash_to_field, Field}; use tempfile::tempdir; + use test_case::test_case; use crate::pbh::db::load_world_chain_db; - use crate::pbh::semaphore::{Proof, SemaphoreProof}; - use crate::pbh::tx::Prefix; + use crate::pbh::semaphore::{ + ExternalNullifier, MonthMarker, Proof, SemaphoreProof, TREE_DEPTH, + }; use crate::pool::ordering::WorldChainOrdering; use crate::pool::root::{WorldChainRootValidator, LATEST_ROOT_SLOT, OP_WORLD_ID}; use crate::pool::tx::WorldChainPooledTransaction; use crate::pool::validator::WorldChainTransactionValidator; - use super::format_date; - fn get_eth_transaction() -> EthPooledTransaction { let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2"; @@ -354,9 +345,10 @@ mod tests { time: chrono::DateTime, pbh_nonce: u16, ) -> SemaphoreProof { - let date_str = format_date(time); - let external_nullifier = format!("{}-{}-{}", Prefix::V1, date_str, pbh_nonce); - create_proof(identity, external_nullifier, tx_hash, 30) + let external_nullifier = + ExternalNullifier::new(MonthMarker::from(time), pbh_nonce).to_string(); + + create_proof(identity, external_nullifier, tx_hash, TREE_DEPTH) } fn create_proof( @@ -526,14 +518,6 @@ mod tests { assert!(res.is_err()); } - #[test] - fn test_format_date() { - let date = chrono::Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(); - let formated = super::format_date(date); - let expected = "012021".to_string(); - assert_eq!(formated, expected); - } - #[test] fn test_validate_root() { let mut validator = world_chain_validator(); @@ -607,26 +591,68 @@ mod tests { } #[test] - fn test_validate_external_nullifier() { + fn validate_external_nullifier_hash_mismatch() { + let external_nullifier = "v1-012025-1"; + let validator = world_chain_validator(); let date = chrono::Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap(); - let valid_external_nullifiers = ["v1-012025-0", "v1-012025-1", "v1-012025-29"]; - let invalid_external_nullifiers = [ - "v0-012025-0", - "v1-022025-0", - "v1-002025-0", - "v1-012025-30", - "v1-012025", - "12025-0", - "v1-012025-0-0", - ]; - for valid in valid_external_nullifiers.iter() { - validator.validate_external_nullifier(date, valid).unwrap(); - } - for invalid in invalid_external_nullifiers.iter() { - let res = validator.validate_external_nullifier(date, invalid); - assert!(res.is_err()); - } + + let semaphore_proof = SemaphoreProof { + external_nullifier: external_nullifier.to_string(), + external_nullifier_hash: Field::ZERO, + nullifier_hash: Field::ZERO, + signal_hash: Field::ZERO, + root: Field::ZERO, + proof: Default::default(), + }; + + let res = validator.validate_external_nullifier(date, &semaphore_proof); + assert!(res.is_err()); + } + + #[test_case("v1-012025-0")] + #[test_case("v1-012025-1")] + #[test_case("v1-012025-29")] + fn validate_external_nullifier_valid(external_nullifier: &str) { + let validator = world_chain_validator(); + let date = chrono::Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap(); + + let semaphore_proof = SemaphoreProof { + external_nullifier: external_nullifier.to_string(), + external_nullifier_hash: hash_to_field(external_nullifier.as_bytes()), + nullifier_hash: Field::ZERO, + signal_hash: Field::ZERO, + root: Field::ZERO, + proof: Default::default(), + }; + + validator + .validate_external_nullifier(date, &semaphore_proof) + .unwrap(); + } + + #[test_case("v0-012025-0")] + #[test_case("v1-022025-0")] + #[test_case("v1-002025-0")] + #[test_case("v1-012025-30")] + #[test_case("v1-012025")] + #[test_case("12025-0")] + #[test_case("v1-012025-0-0")] + fn validate_external_nullifier_invalid(external_nullifier: &str) { + let validator = world_chain_validator(); + let date = chrono::Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap(); + + let semaphore_proof = SemaphoreProof { + external_nullifier: external_nullifier.to_string(), + external_nullifier_hash: hash_to_field(external_nullifier.as_bytes()), + nullifier_hash: Field::ZERO, + signal_hash: Field::ZERO, + root: Field::ZERO, + proof: Default::default(), + }; + + let res = validator.validate_external_nullifier(date, &semaphore_proof); + assert!(res.is_err()); } #[test] From e499cc319ab32ba6968f4c31615c3d16405e6844 Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Wed, 2 Oct 2024 15:35:42 +0200 Subject: [PATCH 2/3] WIP: toolkit --- world-chain-builder/Cargo.lock | 36 +++++++++++++++++++++++++++++- world-chain-builder/Cargo.toml | 8 ++++++- world-chain-builder/bin/toolkit.rs | 28 +++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 world-chain-builder/bin/toolkit.rs diff --git a/world-chain-builder/Cargo.lock b/world-chain-builder/Cargo.lock index cc7dd80c..7934321e 100644 --- a/world-chain-builder/Cargo.lock +++ b/world-chain-builder/Cargo.lock @@ -3464,7 +3464,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -9514,6 +9514,39 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "test-case-core", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -11151,6 +11184,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", + "test-case", "thiserror", "tikv-jemallocator", "tokio", diff --git a/world-chain-builder/Cargo.toml b/world-chain-builder/Cargo.toml index 1722ebe2..7d8e8b5c 100644 --- a/world-chain-builder/Cargo.toml +++ b/world-chain-builder/Cargo.toml @@ -3,7 +3,7 @@ name = "world-chain-builder" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +default-run = "world-chain-builder" [dependencies] # reth @@ -61,6 +61,7 @@ op-alloy-network = "0.2" alloy-consensus = "0.3" alloy-network = "0.3" alloy-primitives = "0.8" +# alloy-provider = "0.3" alloy-rpc-types-eth = "0.3" alloy-rpc-types = "0.3" alloy-rlp = "0.3" @@ -92,6 +93,7 @@ tikv-jemallocator = { version = "0.6.0", optional = true } [dev-dependencies] tempfile = "3" +test-case = "3" ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } reth-provider = { git = "https://github.com/ewoolsey/reth", rev = "b2848f", features = [ "test-utils", @@ -107,3 +109,7 @@ jemalloc = ["tikv-jemallocator"] [[bin]] name = "world-chain-builder" path = "bin/world-chain-builder.rs" + +[[bin]] +name = "toolkit" +path = "bin/toolkit.rs" diff --git a/world-chain-builder/bin/toolkit.rs b/world-chain-builder/bin/toolkit.rs new file mode 100644 index 00000000..8dffbce4 --- /dev/null +++ b/world-chain-builder/bin/toolkit.rs @@ -0,0 +1,28 @@ +use clap::Parser; + +#[derive(Debug, Clone, Parser)] +struct Args { + #[clap(subcommand)] + cmd: Cmd, +} + +#[derive(Debug, Clone, Parser)] +enum Cmd { + Prove(ProveArgs), +} + +#[derive(Debug, Clone, Parser)] +struct ProveArgs { + #[clap(short, long)] + tx_index: usize, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + dotenvy::dotenv().ok(); + + let args = Args::parse(); + println!("{:?}", args); + + Ok(()) +} From c74472ad47be6a22eb0c6c5a88cb713c8a4d98ff Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Wed, 2 Oct 2024 18:49:54 +0200 Subject: [PATCH 3/3] Rename + handle edge case --- world-chain-builder/src/pbh/semaphore.rs | 24 ++++++------- world-chain-builder/src/pool/validator.rs | 42 ++++++++++++++++++----- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/world-chain-builder/src/pbh/semaphore.rs b/world-chain-builder/src/pbh/semaphore.rs index db13c57a..a889882d 100644 --- a/world-chain-builder/src/pbh/semaphore.rs +++ b/world-chain-builder/src/pbh/semaphore.rs @@ -60,18 +60,18 @@ pub struct SemaphoreProof { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ExternalNullifier { - pub month: MonthMarker, + pub month: DateMarker, pub nonce: u16, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MonthMarker { +pub struct DateMarker { pub year: i32, pub month: u32, } impl ExternalNullifier { - pub fn new(month: MonthMarker, nonce: u16) -> Self { + pub fn new(month: DateMarker, nonce: u16) -> Self { Self { month, nonce } } } @@ -137,13 +137,13 @@ impl FromStr for ExternalNullifier { } } -impl MonthMarker { +impl DateMarker { pub fn new(year: i32, month: u32) -> Self { Self { year, month } } } -impl From for MonthMarker +impl From for DateMarker where T: Datelike, { @@ -155,8 +155,8 @@ where } } -impl From for NaiveDate { - fn from(value: MonthMarker) -> Self { +impl From for NaiveDate { + fn from(value: DateMarker) -> Self { NaiveDate::from_ymd_opt(value.year, value.month, 1).unwrap() } } @@ -173,7 +173,7 @@ pub enum MonthMarkerParsingError { InvalidYear(std::num::ParseIntError), } -impl FromStr for MonthMarker { +impl FromStr for DateMarker { type Err = MonthMarkerParsingError; fn from_str(s: &str) -> Result { @@ -193,11 +193,11 @@ impl FromStr for MonthMarker { return Err(MonthMarkerParsingError::MonthOutOfRange { month }); } - Ok(MonthMarker { year, month }) + Ok(DateMarker { year, month }) } } -impl std::fmt::Display for MonthMarker { +impl std::fmt::Display for DateMarker { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:02}{:04}", self.month, self.year) } @@ -252,7 +252,7 @@ mod test { #[test_case("022024")] #[test_case("022025")] fn parse_month_marker_roundtrip(s: &str) { - let m: MonthMarker = s.parse().unwrap(); + let m: DateMarker = s.parse().unwrap(); assert_eq!(m.to_string(), s); } @@ -263,6 +263,6 @@ mod test { #[test_case("" ; "empty")] #[test_case("23012024" ; "too long")] fn parse_month_marker_invalid(s: &str) { - s.parse::().unwrap_err(); + s.parse::().unwrap_err(); } } diff --git a/world-chain-builder/src/pool/validator.rs b/world-chain-builder/src/pool/validator.rs index b37a1763..5e476f6d 100644 --- a/world-chain-builder/src/pool/validator.rs +++ b/world-chain-builder/src/pool/validator.rs @@ -19,7 +19,7 @@ use super::ordering::WorldChainOrdering; use super::root::WorldChainRootValidator; use super::tx::{WorldChainPoolTransaction, WorldChainPooledTransaction}; use crate::pbh::db::{ExecutedPbhNullifierTable, ValidatedPbhTransactionTable}; -use crate::pbh::semaphore::{ExternalNullifier, MonthMarker, SemaphoreProof, TREE_DEPTH}; +use crate::pbh::semaphore::{DateMarker, ExternalNullifier, SemaphoreProof, TREE_DEPTH}; /// Type alias for World Chain transaction pool pub type WorldChainTransactionPool = Pool< @@ -104,8 +104,15 @@ where .map_err(WorldChainTransactionPoolInvalid::InvalidExternalNullifier) .map_err(TransactionValidationError::from)?; - let current_date = MonthMarker::from(date); - if external_nullifier.month != current_date { + // In most cases these will be the same value, but at the month boundary + // we'll still accept the previous month if the transaction is at most a minute late + // or the next month if the transaction is at most a minute early + let valid_dates = vec![ + DateMarker::from(date - chrono::Duration::minutes(1)), + DateMarker::from(date), + DateMarker::from(date + chrono::Duration::minutes(1)), + ]; + if valid_dates.iter().all(|d| external_nullifier.month != *d) { return Err(WorldChainTransactionPoolInvalid::InvalidExternalNullifierPeriod.into()); } @@ -285,9 +292,7 @@ mod tests { use test_case::test_case; use crate::pbh::db::load_world_chain_db; - use crate::pbh::semaphore::{ - ExternalNullifier, MonthMarker, Proof, SemaphoreProof, TREE_DEPTH, - }; + use crate::pbh::semaphore::{DateMarker, ExternalNullifier, Proof, SemaphoreProof, TREE_DEPTH}; use crate::pool::ordering::WorldChainOrdering; use crate::pool::root::{WorldChainRootValidator, LATEST_ROOT_SLOT, OP_WORLD_ID}; use crate::pool::tx::WorldChainPooledTransaction; @@ -346,7 +351,7 @@ mod tests { pbh_nonce: u16, ) -> SemaphoreProof { let external_nullifier = - ExternalNullifier::new(MonthMarker::from(time), pbh_nonce).to_string(); + ExternalNullifier::new(DateMarker::from(time), pbh_nonce).to_string(); create_proof(identity, external_nullifier, tx_hash, TREE_DEPTH) } @@ -631,8 +636,29 @@ mod tests { .unwrap(); } + #[test_case("v1-012025-0", "2024-12-31 23:59:30Z" ; "a minute early")] + #[test_case("v1-012025-0", "2025-02-01 00:00:30Z" ; "a minute late")] + fn validate_external_nullifier_at_time(external_nullifier: &str, time: &str) { + let validator = world_chain_validator(); + let date: chrono::DateTime = time.parse().unwrap(); + + let semaphore_proof = SemaphoreProof { + external_nullifier: external_nullifier.to_string(), + external_nullifier_hash: hash_to_field(external_nullifier.as_bytes()), + nullifier_hash: Field::ZERO, + signal_hash: Field::ZERO, + root: Field::ZERO, + proof: Default::default(), + }; + + validator + .validate_external_nullifier(date, &semaphore_proof) + .unwrap(); + } + #[test_case("v0-012025-0")] #[test_case("v1-022025-0")] + #[test_case("v1-122024-0")] #[test_case("v1-002025-0")] #[test_case("v1-012025-30")] #[test_case("v1-012025")] @@ -640,7 +666,7 @@ mod tests { #[test_case("v1-012025-0-0")] fn validate_external_nullifier_invalid(external_nullifier: &str) { let validator = world_chain_validator(); - let date = chrono::Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap(); + let date = chrono::Utc.with_ymd_and_hms(2025, 1, 1, 12, 0, 0).unwrap(); let semaphore_proof = SemaphoreProof { external_nullifier: external_nullifier.to_string(),