diff --git a/Cargo.lock b/Cargo.lock index c3b8c5f8..cf582227 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1019,19 +1019,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bitcoin-io" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" - [[package]] name = "bitcoin_hashes" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ - "bitcoin-io", "hex-conservative", ] @@ -1522,6 +1515,7 @@ dependencies = [ "log", "logos-blockchain-common-http-client", "nssa", + "nssa_core", "serde", "serde_with", "sha2", @@ -3977,7 +3971,6 @@ dependencies = [ "nssa", "nssa_core", "rand 0.8.5", - "secp256k1", "serde", "sha2", "thiserror 2.0.18", @@ -5269,13 +5262,13 @@ dependencies = [ "env_logger", "hex", "hex-literal 1.1.0", + "k256", "log", "nssa_core", "rand 0.8.5", "risc0-binfmt", "risc0-build", "risc0-zkvm", - "secp256k1", "serde", "serde_with", "sha2", @@ -7086,26 +7079,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" -dependencies = [ - "bitcoin_hashes", - "rand 0.9.2", - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "3.7.0" diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 8184385d..b6867713 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin index 1caccf9b..bcd528f1 100644 Binary files a/artifacts/program_methods/associated_token_account.bin and b/artifacts/program_methods/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index ad874af8..142397f7 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index 12c5869f..280a834f 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index b164e8af..4cd06c56 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index afdf4471..55880e41 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index e03e182a..b9c82387 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index e8a36cb6..a740bdb8 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index 301b6acc..112ca113 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin index 296d9567..a130510b 100644 Binary files a/artifacts/test_program_methods/changer_claimer.bin and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index 556116b4..41a5cb3b 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 8d740cd0..3dddebe1 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index dcaf98dd..1d682ec3 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin index cda941bb..c68496ab 100644 Binary files a/artifacts/test_program_methods/malicious_authorization_changer.bin and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index aef6de74..ffd29461 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index 3b0cf456..a2bbecd8 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index 4a3df239..b44b1233 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index 82bbc20f..e006fc75 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index 26ecb622..da811f60 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index e3a9316f..3963873e 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index 8b5b0ae1..08db47f0 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/artifacts/test_program_methods/validity_window.bin b/artifacts/test_program_methods/validity_window.bin index bc7b4174..ceb5ae74 100644 Binary files a/artifacts/test_program_methods/validity_window.bin and b/artifacts/test_program_methods/validity_window.bin differ diff --git a/artifacts/test_program_methods/validity_window_chain_caller.bin b/artifacts/test_program_methods/validity_window_chain_caller.bin index 33efa941..a7661f03 100644 Binary files a/artifacts/test_program_methods/validity_window_chain_caller.bin and b/artifacts/test_program_methods/validity_window_chain_caller.bin differ diff --git a/common/Cargo.toml b/common/Cargo.toml index a3884b70..0ae0b220 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] nssa.workspace = true +nssa_core.workspace = true anyhow.workspace = true thiserror.workspace = true diff --git a/common/src/block.rs b/common/src/block.rs index 01ba586e..6decc390 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,4 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use nssa_core::{BlockId, Timestamp}; use serde::{Deserialize, Serialize}; use sha2::{Digest as _, Sha256, digest::FixedOutput as _}; @@ -6,8 +7,6 @@ use crate::{HashType, transaction::NSSATransaction}; pub type MantleMsgId = [u8; 32]; pub type BlockHash = HashType; -pub type BlockId = u64; -pub type TimeStamp = u64; #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct BlockMeta { @@ -35,7 +34,7 @@ pub struct BlockHeader { pub block_id: BlockId, pub prev_block_hash: BlockHash, pub hash: BlockHash, - pub timestamp: TimeStamp, + pub timestamp: Timestamp, pub signature: nssa::Signature, } @@ -75,7 +74,7 @@ impl<'de> Deserialize<'de> for Block { pub struct HashableBlockData { pub block_id: BlockId, pub prev_block_hash: BlockHash, - pub timestamp: TimeStamp, + pub timestamp: Timestamp, pub transactions: Vec, } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 1862dcc8..ea0b9819 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,9 +1,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use log::warn; use nssa::{AccountId, V03State}; +use nssa_core::{BlockId, Timestamp}; use serde::{Deserialize, Serialize}; -use crate::{HashType, block::BlockId}; +use crate::HashType; #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum NSSATransaction { @@ -69,11 +70,12 @@ impl NSSATransaction { self, state: &mut V03State, block_id: BlockId, + timestamp: Timestamp, ) -> Result { match &self { - Self::Public(tx) => state.transition_from_public_transaction(tx, block_id), + Self::Public(tx) => state.transition_from_public_transaction(tx, block_id, timestamp), Self::PrivacyPreserving(tx) => { - state.transition_from_privacy_preserving_transaction(tx, block_id) + state.transition_from_privacy_preserving_transaction(tx, block_id, timestamp) } Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx), } diff --git a/explorer_service/src/pages/transaction_page.rs b/explorer_service/src/pages/transaction_page.rs index ed3d8aac..0a3fc8e2 100644 --- a/explorer_service/src/pages/transaction_page.rs +++ b/explorer_service/src/pages/transaction_page.rs @@ -177,13 +177,13 @@ pub fn TransactionPage() -> impl IntoView { encrypted_private_post_states, new_commitments, new_nullifiers, - validity_window + block_validity_window, + timestamp_validity_window, } = message; let WitnessSet { signatures_and_public_keys: _, proof, } = witness_set; - let proof_len = proof.map_or(0, |p| p.0.len()); view! {
@@ -214,8 +214,12 @@ pub fn TransactionPage() -> impl IntoView { {format!("{proof_len} bytes")}
- "Validity Window:" - {validity_window.to_string()} + "Block Validity Window:" + {block_validity_window.to_string()} +
+
+ "Timestamp Validity Window:" + {timestamp_validity_window.to_string()}
diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index e4534f76..7faf5376 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -3,10 +3,11 @@ use std::{path::Path, sync::Arc}; use anyhow::Result; use bedrock_client::HeaderId; use common::{ - block::{BedrockStatus, Block, BlockId}, + block::{BedrockStatus, Block}, transaction::NSSATransaction, }; use nssa::{Account, AccountId, V03State}; +use nssa_core::BlockId; use storage::indexer::RocksDBIO; use tokio::sync::RwLock; @@ -125,7 +126,11 @@ impl IndexerStore { transaction .clone() .transaction_stateless_check()? - .execute_check_on_state(&mut state_guard, block.header.block_id)?; + .execute_check_on_state( + &mut state_guard, + block.header.block_id, + block.header.timestamp, + )?; } } diff --git a/indexer/service/protocol/src/convert.rs b/indexer/service/protocol/src/convert.rs index 2777b512..eb79fa34 100644 --- a/indexer/service/protocol/src/convert.rs +++ b/indexer/service/protocol/src/convert.rs @@ -287,7 +287,8 @@ impl From for PrivacyPre encrypted_private_post_states, new_commitments, new_nullifiers, - validity_window, + block_validity_window, + timestamp_validity_window, } = value; Self { public_account_ids: public_account_ids.into_iter().map(Into::into).collect(), @@ -302,7 +303,8 @@ impl From for PrivacyPre .into_iter() .map(|(n, d)| (n.into(), d.into())) .collect(), - validity_window: validity_window.into(), + block_validity_window: block_validity_window.into(), + timestamp_validity_window: timestamp_validity_window.into(), } } } @@ -318,7 +320,8 @@ impl TryFrom for nssa::privacy_preserving_transaction: encrypted_private_post_states, new_commitments, new_nullifiers, - validity_window, + block_validity_window, + timestamp_validity_window, } = value; Ok(Self { public_account_ids: public_account_ids.into_iter().map(Into::into).collect(), @@ -340,7 +343,10 @@ impl TryFrom for nssa::privacy_preserving_transaction: .into_iter() .map(|(n, d)| (n.into(), d.into())) .collect(), - validity_window: validity_window + block_validity_window: block_validity_window + .try_into() + .map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?, + timestamp_validity_window: timestamp_validity_window .try_into() .map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?, }) @@ -692,13 +698,13 @@ impl From for common::HashType { // ValidityWindow conversions // ============================================================================ -impl From for ValidityWindow { - fn from(value: nssa_core::program::ValidityWindow) -> Self { +impl From> for ValidityWindow { + fn from(value: nssa_core::program::ValidityWindow) -> Self { Self((value.start(), value.end())) } } -impl TryFrom for nssa_core::program::ValidityWindow { +impl TryFrom for nssa_core::program::ValidityWindow { type Error = nssa_core::program::InvalidWindow; fn try_from(value: ValidityWindow) -> Result { diff --git a/indexer/service/protocol/src/lib.rs b/indexer/service/protocol/src/lib.rs index 0d8a7e14..59e936bf 100644 --- a/indexer/service/protocol/src/lib.rs +++ b/indexer/service/protocol/src/lib.rs @@ -235,7 +235,8 @@ pub struct PrivacyPreservingMessage { pub encrypted_private_post_states: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, - pub validity_window: ValidityWindow, + pub block_validity_window: ValidityWindow, + pub timestamp_validity_window: ValidityWindow, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] diff --git a/indexer/service/src/mock_service.rs b/indexer/service/src/mock_service.rs index c5891b41..09ae96f5 100644 --- a/indexer/service/src/mock_service.rs +++ b/indexer/service/src/mock_service.rs @@ -124,7 +124,8 @@ impl MockIndexerService { indexer_service_protocol::Nullifier([tx_idx as u8; 32]), CommitmentSetDigest([0xff; 32]), )], - validity_window: ValidityWindow((None, None)), + block_validity_window: ValidityWindow((None, None)), + timestamp_validity_window: ValidityWindow((None, None)), }, witness_set: WitnessSet { signatures_and_public_keys: vec![], diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index f9cd9239..998f6d71 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -5,7 +5,7 @@ use crate::{ NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata}, encryption::Ciphertext, - program::{ProgramId, ProgramOutput, ValidityWindow}, + program::{BlockValidityWindow, ProgramId, ProgramOutput, TimestampValidityWindow}, }; #[derive(Serialize, Deserialize)] @@ -36,7 +36,8 @@ pub struct PrivacyPreservingCircuitOutput { pub ciphertexts: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, - pub validity_window: ValidityWindow, + pub block_validity_window: BlockValidityWindow, + pub timestamp_validity_window: TimestampValidityWindow, } #[cfg(feature = "host")] @@ -102,7 +103,8 @@ mod tests { ), [0xab; 32], )], - validity_window: (Some(1), None).try_into().unwrap(), + block_validity_window: (1..).into(), + timestamp_validity_window: TimestampValidityWindow::new_unbounded(), }; let bytes = output.to_bytes(); let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 8014c7ca..a4fcdee1 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -21,3 +21,7 @@ pub mod program; #[cfg(feature = "host")] pub mod error; + +pub type BlockId = u64; +/// Unix timestamp in milliseconds. +pub type Timestamp = u64; diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index e88534d0..673e09b3 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -5,7 +5,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; -use crate::account::{Account, AccountId, AccountWithMetadata}; +use crate::{ + BlockId, Timestamp, + account::{Account, AccountId, AccountWithMetadata}, +}; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; pub const MAX_NUMBER_CHAINED_CALLS: usize = 10; @@ -171,20 +174,21 @@ impl AccountPostState { } } -pub type BlockId = u64; +pub type BlockValidityWindow = ValidityWindow; +pub type TimestampValidityWindow = ValidityWindow; #[derive(Clone, Copy, Serialize, Deserialize)] #[cfg_attr( any(feature = "host", test), derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize) )] -pub struct ValidityWindow { - from: Option, - to: Option, +pub struct ValidityWindow { + from: Option, + to: Option, } -impl ValidityWindow { - /// Creates a window with no bounds, valid for every block ID. +impl ValidityWindow { + /// Creates a window with no bounds. #[must_use] pub const fn new_unbounded() -> Self { Self { @@ -192,42 +196,42 @@ impl ValidityWindow { to: None, } } +} - /// Returns `true` if `id` falls within the half-open range `[from, to)`. - /// A `None` bound on either side is treated as unbounded in that direction. +impl ValidityWindow { + /// Valid for values in the range [from, to), where `from` is included and `to` is excluded. #[must_use] - pub fn is_valid_for_block_id(&self, id: BlockId) -> bool { - self.from.is_none_or(|start| id >= start) && self.to.is_none_or(|end| id < end) + pub fn is_valid_for(&self, value: T) -> bool { + self.from.is_none_or(|start| value >= start) && self.to.is_none_or(|end| value < end) } /// Returns `Err(InvalidWindow)` if both bounds are set and `from >= to`. - const fn check_window(&self) -> Result<(), InvalidWindow> { - if let (Some(from_id), Some(until_id)) = (self.from, self.to) - && from_id >= until_id + fn check_window(&self) -> Result<(), InvalidWindow> { + if let (Some(from), Some(to)) = (self.from, self.to) + && from >= to { - Err(InvalidWindow) - } else { - Ok(()) + return Err(InvalidWindow); } + Ok(()) } - /// Inclusive lower bound. `None` means the window starts at the beginning of the chain. + /// Inclusive lower bound. `None` means no lower bound. #[must_use] - pub const fn start(&self) -> Option { + pub const fn start(&self) -> Option { self.from } - /// Exclusive upper bound. `None` means the window has no expiry. + /// Exclusive upper bound. `None` means no upper bound. #[must_use] - pub const fn end(&self) -> Option { + pub const fn end(&self) -> Option { self.to } } -impl TryFrom<(Option, Option)> for ValidityWindow { +impl TryFrom<(Option, Option)> for ValidityWindow { type Error = InvalidWindow; - fn try_from(value: (Option, Option)) -> Result { + fn try_from(value: (Option, Option)) -> Result { let this = Self { from: value.0, to: value.1, @@ -237,16 +241,16 @@ impl TryFrom<(Option, Option)> for ValidityWindow { } } -impl TryFrom> for ValidityWindow { +impl TryFrom> for ValidityWindow { type Error = InvalidWindow; - fn try_from(value: std::ops::Range) -> Result { + fn try_from(value: std::ops::Range) -> Result { (Some(value.start), Some(value.end)).try_into() } } -impl From> for ValidityWindow { - fn from(value: std::ops::RangeFrom) -> Self { +impl From> for ValidityWindow { + fn from(value: std::ops::RangeFrom) -> Self { Self { from: Some(value.start), to: None, @@ -254,8 +258,8 @@ impl From> for ValidityWindow { } } -impl From> for ValidityWindow { - fn from(value: std::ops::RangeTo) -> Self { +impl From> for ValidityWindow { + fn from(value: std::ops::RangeTo) -> Self { Self { from: None, to: Some(value.end), @@ -263,7 +267,7 @@ impl From> for ValidityWindow { } } -impl From for ValidityWindow { +impl From for ValidityWindow { fn from(_: std::ops::RangeFull) -> Self { Self::new_unbounded() } @@ -285,8 +289,10 @@ pub struct ProgramOutput { pub post_states: Vec, /// The list of chained calls to other programs. pub chained_calls: Vec, - /// The window where the program output is valid. - pub validity_window: ValidityWindow, + /// The block ID window where the program output is valid. + pub block_validity_window: BlockValidityWindow, + /// The timestamp window where the program output is valid. + pub timestamp_validity_window: TimestampValidityWindow, } impl ProgramOutput { @@ -300,7 +306,8 @@ impl ProgramOutput { pre_states, post_states, chained_calls: Vec::new(), - validity_window: ValidityWindow::new_unbounded(), + block_validity_window: ValidityWindow::new_unbounded(), + timestamp_validity_window: ValidityWindow::new_unbounded(), } } @@ -313,19 +320,52 @@ impl ProgramOutput { self } - /// Sets the validity window from an infallible range conversion (`1..`, `..5`, `..`). - pub fn with_validity_window>(mut self, window: W) -> Self { - self.validity_window = window.into(); + /// Sets the block ID validity window from an infallible range conversion (`1..`, `..5`, `..`). + pub fn with_block_validity_window>(mut self, window: W) -> Self { + self.block_validity_window = window.into(); self } - /// Sets the validity window from a fallible range conversion (`1..5`). + /// Sets the block ID validity window from a fallible range conversion (`1..5`). /// Returns `Err` if the range is empty. - pub fn try_with_validity_window>( + pub fn try_with_block_validity_window< + W: TryInto, + >( mut self, window: W, ) -> Result { - self.validity_window = window.try_into()?; + self.block_validity_window = window.try_into()?; + Ok(self) + } + + /// Sets the timestamp validity window from an infallible range conversion. + pub fn with_timestamp_validity_window>( + mut self, + window: W, + ) -> Self { + self.timestamp_validity_window = window.into(); + self + } + + /// Sets the timestamp validity window from a fallible range conversion. + /// Returns `Err` if the range is empty. + pub fn try_with_timestamp_validity_window< + W: TryInto, + >( + mut self, + window: W, + ) -> Result { + self.timestamp_validity_window = window.try_into()?; + Ok(self) + } + + pub fn valid_from_timestamp(mut self, ts: Option) -> Result { + self.timestamp_validity_window = (ts, self.timestamp_validity_window.end()).try_into()?; + Ok(self) + } + + pub fn valid_until_timestamp(mut self, ts: Option) -> Result { + self.timestamp_validity_window = (self.timestamp_validity_window.start(), ts).try_into()?; Ok(self) } } @@ -482,128 +522,131 @@ mod tests { use super::*; #[test] - fn validity_window_unbounded_accepts_any_block() { - let w = ValidityWindow::new_unbounded(); - assert!(w.is_valid_for_block_id(0)); - assert!(w.is_valid_for_block_id(u64::MAX)); + fn validity_window_unbounded_accepts_any_value() { + let w: ValidityWindow = ValidityWindow::new_unbounded(); + assert!(w.is_valid_for(0)); + assert!(w.is_valid_for(u64::MAX)); } #[test] fn validity_window_bounded_range_includes_from_excludes_to() { - let w: ValidityWindow = (Some(5), Some(10)).try_into().unwrap(); - assert!(!w.is_valid_for_block_id(4)); - assert!(w.is_valid_for_block_id(5)); - assert!(w.is_valid_for_block_id(9)); - assert!(!w.is_valid_for_block_id(10)); + let w: ValidityWindow = (Some(5), Some(10)).try_into().unwrap(); + assert!(!w.is_valid_for(4)); + assert!(w.is_valid_for(5)); + assert!(w.is_valid_for(9)); + assert!(!w.is_valid_for(10)); } #[test] fn validity_window_only_from_bound() { - let w: ValidityWindow = (Some(5), None).try_into().unwrap(); - assert!(!w.is_valid_for_block_id(4)); - assert!(w.is_valid_for_block_id(5)); - assert!(w.is_valid_for_block_id(u64::MAX)); + let w: ValidityWindow = (Some(5), None).try_into().unwrap(); + assert!(!w.is_valid_for(4)); + assert!(w.is_valid_for(5)); + assert!(w.is_valid_for(u64::MAX)); } #[test] fn validity_window_only_to_bound() { - let w: ValidityWindow = (None, Some(5)).try_into().unwrap(); - assert!(w.is_valid_for_block_id(0)); - assert!(w.is_valid_for_block_id(4)); - assert!(!w.is_valid_for_block_id(5)); + let w: ValidityWindow = (None, Some(5)).try_into().unwrap(); + assert!(w.is_valid_for(0)); + assert!(w.is_valid_for(4)); + assert!(!w.is_valid_for(5)); } #[test] fn validity_window_adjacent_bounds_are_invalid() { // [5, 5) is an empty range — from == to - assert!(ValidityWindow::try_from((Some(5), Some(5))).is_err()); + assert!(ValidityWindow::::try_from((Some(5), Some(5))).is_err()); } #[test] fn validity_window_inverted_bounds_are_invalid() { - assert!(ValidityWindow::try_from((Some(10), Some(5))).is_err()); + assert!(ValidityWindow::::try_from((Some(10), Some(5))).is_err()); } #[test] fn validity_window_getters_match_construction() { - let w: ValidityWindow = (Some(3), Some(7)).try_into().unwrap(); + let w: ValidityWindow = (Some(3), Some(7)).try_into().unwrap(); assert_eq!(w.start(), Some(3)); assert_eq!(w.end(), Some(7)); } #[test] fn validity_window_getters_for_unbounded() { - let w = ValidityWindow::new_unbounded(); + let w: ValidityWindow = ValidityWindow::new_unbounded(); assert_eq!(w.start(), None); assert_eq!(w.end(), None); } #[test] fn validity_window_from_range() { - let w = ValidityWindow::try_from(5_u64..10).unwrap(); + let w: ValidityWindow = ValidityWindow::try_from(5_u64..10).unwrap(); assert_eq!(w.start(), Some(5)); assert_eq!(w.end(), Some(10)); } #[test] fn validity_window_from_range_empty_is_invalid() { - assert!(ValidityWindow::try_from(5_u64..5).is_err()); + assert!(ValidityWindow::::try_from(5_u64..5).is_err()); } #[test] fn validity_window_from_range_inverted_is_invalid() { let from = 10_u64; let to = 5_u64; - assert!(ValidityWindow::try_from(from..to).is_err()); + assert!(ValidityWindow::::try_from(from..to).is_err()); } #[test] fn validity_window_from_range_from() { - let w: ValidityWindow = (5_u64..).into(); + let w: ValidityWindow = (5_u64..).into(); assert_eq!(w.start(), Some(5)); assert_eq!(w.end(), None); } #[test] fn validity_window_from_range_to() { - let w: ValidityWindow = (..10_u64).into(); + let w: ValidityWindow = (..10_u64).into(); assert_eq!(w.start(), None); assert_eq!(w.end(), Some(10)); } #[test] fn validity_window_from_range_full() { - let w: ValidityWindow = (..).into(); + let w: ValidityWindow = (..).into(); assert_eq!(w.start(), None); assert_eq!(w.end(), None); } #[test] - fn program_output_try_with_validity_window_range() { + fn program_output_try_with_block_validity_window_range() { let output = ProgramOutput::new(vec![], vec![], vec![]) - .try_with_validity_window(10_u64..100) + .try_with_block_validity_window(10_u64..100) .unwrap(); - assert_eq!(output.validity_window.start(), Some(10)); - assert_eq!(output.validity_window.end(), Some(100)); + assert_eq!(output.block_validity_window.start(), Some(10)); + assert_eq!(output.block_validity_window.end(), Some(100)); } #[test] - fn program_output_with_validity_window_range_from() { - let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(10_u64..); - assert_eq!(output.validity_window.start(), Some(10)); - assert_eq!(output.validity_window.end(), None); + fn program_output_with_block_validity_window_range_from() { + let output = + ProgramOutput::new(vec![], vec![], vec![]).with_block_validity_window(10_u64..); + assert_eq!(output.block_validity_window.start(), Some(10)); + assert_eq!(output.block_validity_window.end(), None); } #[test] - fn program_output_with_validity_window_range_to() { - let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(..100_u64); - assert_eq!(output.validity_window.start(), None); - assert_eq!(output.validity_window.end(), Some(100)); + fn program_output_with_block_validity_window_range_to() { + let output = + ProgramOutput::new(vec![], vec![], vec![]).with_block_validity_window(..100_u64); + assert_eq!(output.block_validity_window.start(), None); + assert_eq!(output.block_validity_window.end(), Some(100)); } #[test] - fn program_output_try_with_validity_window_empty_range_fails() { - let result = ProgramOutput::new(vec![], vec![], vec![]).try_with_validity_window(5_u64..5); + fn program_output_try_with_block_validity_window_empty_range_fails() { + let result = + ProgramOutput::new(vec![], vec![], vec![]).try_with_block_validity_window(5_u64..5); assert!(result.is_err()); } diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 755b54f3..85f4a202 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -3,7 +3,7 @@ use nssa_core::{ Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput, account::{Account, Nonce}, encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey}, - program::ValidityWindow, + program::{BlockValidityWindow, TimestampValidityWindow}, }; use sha2::{Digest as _, Sha256}; @@ -53,7 +53,8 @@ pub struct Message { pub encrypted_private_post_states: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, - pub validity_window: ValidityWindow, + pub block_validity_window: BlockValidityWindow, + pub timestamp_validity_window: TimestampValidityWindow, } impl std::fmt::Debug for Message { @@ -79,7 +80,8 @@ impl std::fmt::Debug for Message { ) .field("new_commitments", &self.new_commitments) .field("new_nullifiers", &nullifiers) - .field("validity_window", &self.validity_window) + .field("block_validity_window", &self.block_validity_window) + .field("timestamp_validity_window", &self.timestamp_validity_window) .finish() } } @@ -112,7 +114,8 @@ impl Message { encrypted_private_post_states, new_commitments: output.new_commitments, new_nullifiers: output.new_nullifiers, - validity_window: output.validity_window, + block_validity_window: output.block_validity_window, + timestamp_validity_window: output.timestamp_validity_window, }) } } @@ -123,6 +126,7 @@ pub mod tests { Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, SharedSecretKey, account::Account, encryption::{EphemeralPublicKey, ViewingPublicKey}, + program::{BlockValidityWindow, TimestampValidityWindow}, }; use sha2::{Digest as _, Sha256}; @@ -165,7 +169,8 @@ pub mod tests { encrypted_private_post_states, new_commitments, new_nullifiers, - validity_window: (None, None).try_into().unwrap(), + block_validity_window: BlockValidityWindow::new_unbounded(), + timestamp_validity_window: TimestampValidityWindow::new_unbounded(), } } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index b1c30109..977bb0d0 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -5,17 +5,14 @@ use std::{ use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ - Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, + BlockId, PrivacyPreservingCircuitOutput, Timestamp, account::{Account, AccountWithMetadata}, - program::{BlockId, ValidityWindow}, }; use sha2::{Digest as _, digest::FixedOutput as _}; use super::{message::Message, witness_set::WitnessSet}; use crate::{ - AccountId, V03State, - error::NssaError, - privacy_preserving_transaction::{circuit::Proof, message::EncryptedAccountData}, + AccountId, V03State, error::NssaError, privacy_preserving_transaction::circuit::Proof, }; #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] @@ -37,6 +34,7 @@ impl PrivacyPreservingTransaction { &self, state: &V03State, block_id: BlockId, + timestamp: Timestamp, ) -> Result, NssaError> { let message = &self.message; let witness_set = &self.witness_set; @@ -94,7 +92,9 @@ impl PrivacyPreservingTransaction { } // Verify validity window - if !message.validity_window.is_valid_for_block_id(block_id) { + if !message.block_validity_window.is_valid_for(block_id) + || !message.timestamp_validity_window.is_valid_for(timestamp) + { return Err(NssaError::OutOfValidityWindow); } @@ -115,11 +115,7 @@ impl PrivacyPreservingTransaction { check_privacy_preserving_circuit_proof_is_valid( &witness_set.proof, &public_pre_states, - &message.public_post_states, - &message.encrypted_private_post_states, - &message.new_commitments, - &message.new_nullifiers, - &message.validity_window, + message, )?; // 5. Commitment freshness @@ -177,23 +173,21 @@ impl PrivacyPreservingTransaction { fn check_privacy_preserving_circuit_proof_is_valid( proof: &Proof, public_pre_states: &[AccountWithMetadata], - public_post_states: &[Account], - encrypted_private_post_states: &[EncryptedAccountData], - new_commitments: &[Commitment], - new_nullifiers: &[(Nullifier, CommitmentSetDigest)], - validity_window: &ValidityWindow, + message: &Message, ) -> Result<(), NssaError> { let output = PrivacyPreservingCircuitOutput { public_pre_states: public_pre_states.to_vec(), - public_post_states: public_post_states.to_vec(), - ciphertexts: encrypted_private_post_states + public_post_states: message.public_post_states.clone(), + ciphertexts: message + .encrypted_private_post_states .iter() .cloned() .map(|value| value.ciphertext) .collect(), - new_commitments: new_commitments.to_vec(), - new_nullifiers: new_nullifiers.to_vec(), - validity_window: validity_window.to_owned(), + new_commitments: message.new_commitments.clone(), + new_nullifiers: message.new_nullifiers.clone(), + block_validity_window: message.block_validity_window, + timestamp_validity_window: message.timestamp_validity_window, }; proof .is_valid_for(&output) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 75c15ddc..6a27c0a4 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -3,8 +3,9 @@ use std::collections::{HashMap, HashSet, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; use log::debug; use nssa_core::{ + BlockId, Timestamp, account::{Account, AccountId, AccountWithMetadata}, - program::{BlockId, ChainedCall, Claim, DEFAULT_PROGRAM_ID, validate_execution}, + program::{ChainedCall, Claim, DEFAULT_PROGRAM_ID, validate_execution}, }; use sha2::{Digest as _, digest::FixedOutput as _}; @@ -71,6 +72,7 @@ impl PublicTransaction { &self, state: &V03State, block_id: BlockId, + timestamp: Timestamp, ) -> Result, NssaError> { let message = self.message(); let witness_set = self.witness_set(); @@ -195,9 +197,10 @@ impl PublicTransaction { // Verify validity window ensure!( - program_output - .validity_window - .is_valid_for_block_id(block_id), + program_output.block_validity_window.is_valid_for(block_id) + && program_output + .timestamp_validity_window + .is_valid_for(timestamp), NssaError::OutOfValidityWindow ); @@ -388,7 +391,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state, 1); + let result = tx.validate_and_produce_public_state_diff(&state, 1, 0); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -408,7 +411,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state, 1); + let result = tx.validate_and_produce_public_state_diff(&state, 1, 0); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -429,7 +432,7 @@ pub mod tests { let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state, 1); + let result = tx.validate_and_produce_public_state_diff(&state, 1, 0); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -449,7 +452,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state, 1); + let result = tx.validate_and_produce_public_state_diff(&state, 1, 0); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -465,7 +468,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state, 1); + let result = tx.validate_and_produce_public_state_diff(&state, 1, 0); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 4e7f4e9d..ec37884e 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2,9 +2,10 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ - Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier, + BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier, + Timestamp, account::{Account, AccountId, Nonce}, - program::{BlockId, ProgramId}, + program::ProgramId, }; use crate::{ @@ -159,8 +160,9 @@ impl V03State { &mut self, tx: &PublicTransaction, block_id: BlockId, + timestamp: Timestamp, ) -> Result<(), NssaError> { - let state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?; + let state_diff = tx.validate_and_produce_public_state_diff(self, block_id, timestamp)?; #[expect( clippy::iter_over_hash_type, @@ -184,9 +186,11 @@ impl V03State { &mut self, tx: &PrivacyPreservingTransaction, block_id: BlockId, + timestamp: Timestamp, ) -> Result<(), NssaError> { // 1. Verify the transaction satisfies acceptance criteria - let public_state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?; + let public_state_diff = + tx.validate_and_produce_public_state_diff(self, block_id, timestamp)?; let message = tx.message(); @@ -338,10 +342,11 @@ pub mod tests { use std::collections::HashMap; use nssa_core::{ - Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + BlockId, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + Timestamp, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey}, - program::{BlockId, PdaSeed, ProgramId, ValidityWindow}, + program::{BlockValidityWindow, PdaSeed, ProgramId, TimestampValidityWindow}, }; use crate::{ @@ -576,7 +581,7 @@ pub mod tests { let balance_to_move = 5; let tx = transfer_transaction(from, &key, 0, to, &to_key, 0, balance_to_move); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); assert_eq!(state.get_account_by_id(from).balance, 95); assert_eq!(state.get_account_by_id(to).balance, 5); @@ -598,7 +603,7 @@ pub mod tests { assert!(state.get_account_by_id(from).balance < balance_to_move); let tx = transfer_transaction(from, &from_key, 0, to, &to_key, 0, balance_to_move); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); assert_eq!(state.get_account_by_id(from).balance, 100); @@ -623,7 +628,7 @@ pub mod tests { let balance_to_move = 8; let tx = transfer_transaction(from, &from_key, 0, to, &to_key, 0, balance_to_move); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); assert_eq!(state.get_account_by_id(from).balance, 192); assert_eq!(state.get_account_by_id(to).balance, 108); @@ -652,7 +657,7 @@ pub mod tests { 0, balance_to_move, ); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let balance_to_move = 3; let tx = transfer_transaction( account_id2, @@ -663,7 +668,7 @@ pub mod tests { 0, balance_to_move, ); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); assert_eq!(state.get_account_by_id(account_id1).balance, 95); assert_eq!(state.get_account_by_id(account_id2).balance, 2); @@ -685,7 +690,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -702,7 +707,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -719,7 +724,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -743,7 +748,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -767,7 +772,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -791,7 +796,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -815,7 +820,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -843,7 +848,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -868,7 +873,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -886,7 +891,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -915,7 +920,7 @@ pub mod tests { .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -1108,7 +1113,7 @@ pub mod tests { assert!(!state.private_state.0.contains(&expected_new_commitment)); state - .transition_from_privacy_preserving_transaction(&tx, 1) + .transition_from_privacy_preserving_transaction(&tx, 1, 0) .unwrap(); let sender_post = state.get_account_by_id(sender_keys.account_id()); @@ -1178,7 +1183,7 @@ pub mod tests { assert!(!state.private_state.1.contains(&expected_new_nullifier)); state - .transition_from_privacy_preserving_transaction(&tx, 1) + .transition_from_privacy_preserving_transaction(&tx, 1, 0) .unwrap(); assert_eq!(state.public_state, previous_public_state); @@ -1242,7 +1247,7 @@ pub mod tests { assert!(!state.private_state.1.contains(&expected_new_nullifier)); state - .transition_from_privacy_preserving_transaction(&tx, 1) + .transition_from_privacy_preserving_transaction(&tx, 1, 0) .unwrap(); let recipient_post = state.get_account_by_id(recipient_keys.account_id()); @@ -2170,7 +2175,7 @@ pub mod tests { ); state - .transition_from_privacy_preserving_transaction(&tx, 1) + .transition_from_privacy_preserving_transaction(&tx, 1, 0) .unwrap(); let sender_private_account = Account { @@ -2188,7 +2193,7 @@ pub mod tests { &state, ); - let result = state.transition_from_privacy_preserving_transaction(&tx, 1); + let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); let NssaError::InvalidInput(error_message) = result.err().unwrap() else { @@ -2266,7 +2271,7 @@ pub mod tests { public_transaction::WitnessSet::for_message(&message, &[&from_key, &to_key]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let recipient_post = state.get_account_by_id(to); @@ -2288,7 +2293,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); assert_eq!(state.get_account_by_id(account_id), Account::default()); @@ -2313,7 +2318,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&account_key]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); assert_eq!( state.get_account_by_id(account_id), @@ -2361,7 +2366,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let from_post = state.get_account_by_id(from); let to_post = state.get_account_by_id(to); @@ -2401,7 +2406,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!( result, Err(NssaError::MaxChainedCallsDepthExceeded) @@ -2442,7 +2447,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let from_post = state.get_account_by_id(from); let to_post = state.get_account_by_id(to); @@ -2499,7 +2504,7 @@ pub mod tests { public_transaction::WitnessSet::for_message(&message, &[&from_key, &to_key]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let from_post = state.get_account_by_id(from); let to_post = state.get_account_by_id(to); @@ -2572,7 +2577,7 @@ pub mod tests { let tx = PrivacyPreservingTransaction::new(message, witness_set); state - .transition_from_privacy_preserving_transaction(&tx, 1) + .transition_from_privacy_preserving_transaction(&tx, 1, 0) .unwrap(); let nullifier = Nullifier::for_account_update(&sender_commitment, &sender_keys.nsk); @@ -2690,7 +2695,7 @@ pub mod tests { let transaction = PrivacyPreservingTransaction::new(message, witness_set); state - .transition_from_privacy_preserving_transaction(&transaction, 1) + .transition_from_privacy_preserving_transaction(&transaction, 1, 0) .unwrap(); // Assert @@ -2706,6 +2711,96 @@ pub mod tests { ); } + #[test] + fn pda_mechanism_with_pinata_token_program() { + let pinata_token = Program::pinata_token(); + let token = Program::token(); + + let pinata_definition_id = AccountId::new([1; 32]); + let pinata_token_definition_id = AccountId::new([2; 32]); + // Total supply of pinata token will be in an account under a PDA. + let pinata_token_holding_id = AccountId::from((&pinata_token.id(), &PdaSeed::new([0; 32]))); + let winner_token_holding_id = AccountId::new([3; 32]); + + let expected_winner_account_holding = token_core::TokenHolding::Fungible { + definition_id: pinata_token_definition_id, + balance: 150, + }; + let expected_winner_token_holding_post = Account { + program_owner: token.id(), + data: Data::from(&expected_winner_account_holding), + ..Account::default() + }; + + let mut state = V03State::new_with_genesis_accounts(&[], &[]); + state.add_pinata_token_program(pinata_definition_id); + + // Set up the token accounts directly (bypassing public transactions which + // would require signers for Claim::Authorized). The focus of this test is + // the PDA mechanism in the pinata program's chained call, not token creation. + let total_supply: u128 = 10_000_000; + let token_definition = token_core::TokenDefinition::Fungible { + name: String::from("PINATA"), + total_supply, + metadata_id: None, + }; + let token_holding = token_core::TokenHolding::Fungible { + definition_id: pinata_token_definition_id, + balance: total_supply, + }; + let winner_holding = token_core::TokenHolding::Fungible { + definition_id: pinata_token_definition_id, + balance: 0, + }; + state.force_insert_account( + pinata_token_definition_id, + Account { + program_owner: token.id(), + data: Data::from(&token_definition), + ..Account::default() + }, + ); + state.force_insert_account( + pinata_token_holding_id, + Account { + program_owner: token.id(), + data: Data::from(&token_holding), + ..Account::default() + }, + ); + state.force_insert_account( + winner_token_holding_id, + Account { + program_owner: token.id(), + data: Data::from(&winner_holding), + ..Account::default() + }, + ); + + // Submit a solution to the pinata program to claim the prize + let solution: u128 = 989_106; + let message = public_transaction::Message::try_new( + pinata_token.id(), + vec![ + pinata_definition_id, + pinata_token_holding_id, + winner_token_holding_id, + ], + vec![], + solution, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); + + let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id); + assert_eq!( + winner_token_holding_post, + expected_winner_token_holding_post + ); + } + #[test] fn claiming_mechanism_cannot_claim_initialied_accounts() { let claimer = Program::claimer(); @@ -2727,7 +2822,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -2773,7 +2868,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]); let tx = PublicTransaction::new(message, witness_set); - let res = state.transition_from_public_transaction(&tx, 1); + let res = state.transition_from_public_transaction(&tx, 1, 0); assert!(matches!(res, Err(NssaError::InvalidProgramBehavior))); let sender_post = state.get_account_by_id(sender_id); @@ -2842,7 +2937,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, proof, &[]); let tx = PrivacyPreservingTransaction::new(message, witness_set); - let result = state.transition_from_privacy_preserving_transaction(&tx, 1); + let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0); assert!(result.is_ok()); let nullifier = Nullifier::for_account_initialization(&private_keys.npk()); @@ -2889,7 +2984,7 @@ pub mod tests { let tx = PrivacyPreservingTransaction::new(message, witness_set); state - .transition_from_privacy_preserving_transaction(&tx, 1) + .transition_from_privacy_preserving_transaction(&tx, 1, 0) .unwrap(); let nullifier = Nullifier::for_account_initialization(&private_keys.npk()); @@ -2942,7 +3037,7 @@ pub mod tests { // Claim should succeed assert!( state - .transition_from_privacy_preserving_transaction(&tx, 1) + .transition_from_privacy_preserving_transaction(&tx, 1, 0) .is_ok() ); @@ -2991,7 +3086,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); // Should succeed - no changes made, no claim needed assert!(result.is_ok()); @@ -3016,7 +3111,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx, 1); + let result = state.transition_from_public_transaction(&tx, 1, 0); // Should fail - cannot modify data without claiming the account assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); @@ -3145,7 +3240,7 @@ pub mod tests { validity_window: (Option, Option), block_id: BlockId, ) { - let validity_window: ValidityWindow = validity_window.try_into().unwrap(); + let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap(); let validity_window_program = Program::validity_window(); let account_keys = test_public_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id()); @@ -3154,21 +3249,76 @@ pub mod tests { let account_ids = vec![pre.account_id]; let nonces = vec![]; let program_id = validity_window_program.id(); - let message = public_transaction::Message::try_new( - program_id, - account_ids, - nonces, - validity_window, - ) - .unwrap(); + let instruction = ( + block_validity_window, + TimestampValidityWindow::new_unbounded(), + ); + let message = + public_transaction::Message::try_new(program_id, account_ids, nonces, instruction) + .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); PublicTransaction::new(message, witness_set) }; - let result = state.transition_from_public_transaction(&tx, block_id); - let is_inside_validity_window = match (validity_window.start(), validity_window.end()) { - (Some(s), Some(e)) => s <= block_id && block_id < e, - (Some(s), None) => s <= block_id, - (None, Some(e)) => block_id < e, + let result = state.transition_from_public_transaction(&tx, block_id, 0); + let is_inside_validity_window = + match (block_validity_window.start(), block_validity_window.end()) { + (Some(s), Some(e)) => s <= block_id && block_id < e, + (Some(s), None) => s <= block_id, + (None, Some(e)) => block_id < e, + (None, None) => true, + }; + if is_inside_validity_window { + assert!(result.is_ok()); + } else { + assert!(matches!(result, Err(NssaError::OutOfValidityWindow))); + } + } + + #[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")] + #[test_case::test_case((Some(1), Some(3)), 2; "inside range")] + #[test_case::test_case((Some(1), Some(3)), 0; "below range")] + #[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")] + #[test_case::test_case((Some(1), Some(3)), 4; "above range")] + #[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")] + #[test_case::test_case((Some(1), None), 10; "lower bound only - above")] + #[test_case::test_case((Some(1), None), 0; "lower bound only - below")] + #[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")] + #[test_case::test_case((None, Some(3)), 0; "upper bound only - below")] + #[test_case::test_case((None, Some(3)), 4; "upper bound only - above")] + #[test_case::test_case((None, None), 0; "no bounds - always valid")] + #[test_case::test_case((None, None), 100; "no bounds - always valid 2")] + fn timestamp_validity_window_works_in_public_transactions( + validity_window: (Option, Option), + timestamp: Timestamp, + ) { + let timestamp_validity_window: TimestampValidityWindow = + validity_window.try_into().unwrap(); + let validity_window_program = Program::validity_window(); + let account_keys = test_public_account_keys_1(); + let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id()); + let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let tx = { + let account_ids = vec![pre.account_id]; + let nonces = vec![]; + let program_id = validity_window_program.id(); + let instruction = ( + BlockValidityWindow::new_unbounded(), + timestamp_validity_window, + ); + let message = + public_transaction::Message::try_new(program_id, account_ids, nonces, instruction) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + PublicTransaction::new(message, witness_set) + }; + let result = state.transition_from_public_transaction(&tx, 1, timestamp); + let is_inside_validity_window = match ( + timestamp_validity_window.start(), + timestamp_validity_window.end(), + ) { + (Some(s), Some(e)) => s <= timestamp && timestamp < e, + (Some(s), None) => s <= timestamp, + (None, Some(e)) => timestamp < e, (None, None) => true, }; if is_inside_validity_window { @@ -3195,7 +3345,7 @@ pub mod tests { validity_window: (Option, Option), block_id: BlockId, ) { - let validity_window: ValidityWindow = validity_window.try_into().unwrap(); + let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap(); let validity_window_program = Program::validity_window(); let account_keys = test_private_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk()); @@ -3205,9 +3355,13 @@ pub mod tests { let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk()); let epk = EphemeralPublicKey::from_scalar(esk); + let instruction = ( + block_validity_window, + TimestampValidityWindow::new_unbounded(), + ); let (output, proof) = circuit::execute_and_prove( vec![pre], - Program::serialize_instruction(validity_window).unwrap(), + Program::serialize_instruction(instruction).unwrap(), vec![2], vec![(account_keys.npk(), shared_secret)], vec![], @@ -3227,11 +3381,83 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, proof, &[]); PrivacyPreservingTransaction::new(message, witness_set) }; - let result = state.transition_from_privacy_preserving_transaction(&tx, block_id); - let is_inside_validity_window = match (validity_window.start(), validity_window.end()) { - (Some(s), Some(e)) => s <= block_id && block_id < e, - (Some(s), None) => s <= block_id, - (None, Some(e)) => block_id < e, + let result = state.transition_from_privacy_preserving_transaction(&tx, block_id, 0); + let is_inside_validity_window = + match (block_validity_window.start(), block_validity_window.end()) { + (Some(s), Some(e)) => s <= block_id && block_id < e, + (Some(s), None) => s <= block_id, + (None, Some(e)) => block_id < e, + (None, None) => true, + }; + if is_inside_validity_window { + assert!(result.is_ok()); + } else { + assert!(matches!(result, Err(NssaError::OutOfValidityWindow))); + } + } + + #[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")] + #[test_case::test_case((Some(1), Some(3)), 2; "inside range")] + #[test_case::test_case((Some(1), Some(3)), 0; "below range")] + #[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")] + #[test_case::test_case((Some(1), Some(3)), 4; "above range")] + #[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")] + #[test_case::test_case((Some(1), None), 10; "lower bound only - above")] + #[test_case::test_case((Some(1), None), 0; "lower bound only - below")] + #[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")] + #[test_case::test_case((None, Some(3)), 0; "upper bound only - below")] + #[test_case::test_case((None, Some(3)), 4; "upper bound only - above")] + #[test_case::test_case((None, None), 0; "no bounds - always valid")] + #[test_case::test_case((None, None), 100; "no bounds - always valid 2")] + fn timestamp_validity_window_works_in_privacy_preserving_transactions( + validity_window: (Option, Option), + timestamp: Timestamp, + ) { + let timestamp_validity_window: TimestampValidityWindow = + validity_window.try_into().unwrap(); + let validity_window_program = Program::validity_window(); + let account_keys = test_private_account_keys_1(); + let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk()); + let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let tx = { + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk()); + let epk = EphemeralPublicKey::from_scalar(esk); + + let instruction = ( + BlockValidityWindow::new_unbounded(), + timestamp_validity_window, + ); + let (output, proof) = circuit::execute_and_prove( + vec![pre], + Program::serialize_instruction(instruction).unwrap(), + vec![2], + vec![(account_keys.npk(), shared_secret)], + vec![], + vec![None], + &validity_window_program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![(account_keys.npk(), account_keys.vpk(), epk)], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + PrivacyPreservingTransaction::new(message, witness_set) + }; + let result = state.transition_from_privacy_preserving_transaction(&tx, 1, timestamp); + let is_inside_validity_window = match ( + timestamp_validity_window.start(), + timestamp_validity_window.end(), + ) { + (Some(s), Some(e)) => s <= timestamp && timestamp < e, + (Some(s), None) => s <= timestamp, + (None, Some(e)) => timestamp < e, (None, None) => true, }; if is_inside_validity_window { diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index c561d139..e53334f9 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -10,8 +10,9 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Nonce}, compute_digest_for_path, program::{ - AccountPostState, ChainedCall, Claim, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, - ProgramId, ProgramOutput, ValidityWindow, validate_execution, + AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID, + MAX_NUMBER_CHAINED_CALLS, ProgramId, ProgramOutput, TimestampValidityWindow, + validate_execution, }, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -20,7 +21,8 @@ use risc0_zkvm::{guest::env, serde::to_vec}; struct ExecutionState { pre_states: Vec, post_states: HashMap, - validity_window: ValidityWindow, + block_validity_window: BlockValidityWindow, + timestamp_validity_window: TimestampValidityWindow, } impl ExecutionState { @@ -30,23 +32,40 @@ impl ExecutionState { program_id: ProgramId, program_outputs: Vec, ) -> Self { - let valid_from_id = program_outputs + let block_valid_from = program_outputs .iter() - .filter_map(|output| output.validity_window.start()) + .filter_map(|output| output.block_validity_window.start()) .max(); - let valid_until_id = program_outputs + let block_valid_until = program_outputs .iter() - .filter_map(|output| output.validity_window.end()) + .filter_map(|output| output.block_validity_window.end()) + .min(); + let ts_valid_from = program_outputs + .iter() + .filter_map(|output| output.timestamp_validity_window.start()) + .max(); + let ts_valid_until = program_outputs + .iter() + .filter_map(|output| output.timestamp_validity_window.end()) .min(); - let validity_window = (valid_from_id, valid_until_id).try_into().expect( - "There should be non empty intersection in the program output validity windows", - ); + let block_validity_window: BlockValidityWindow = (block_valid_from, block_valid_until) + .try_into() + .expect( + "There should be non empty intersection in the program output block validity windows", + ); + let timestamp_validity_window: TimestampValidityWindow = + (ts_valid_from, ts_valid_until) + .try_into() + .expect( + "There should be non empty intersection in the program output timestamp validity windows", + ); let mut execution_state = Self { pre_states: Vec::new(), post_states: HashMap::new(), - validity_window, + block_validity_window, + timestamp_validity_window, }; let Some(first_output) = program_outputs.first() else { @@ -277,7 +296,8 @@ fn compute_circuit_output( ciphertexts: Vec::new(), new_commitments: Vec::new(), new_nullifiers: Vec::new(), - validity_window: execution_state.validity_window, + block_validity_window: execution_state.block_validity_window, + timestamp_validity_window: execution_state.timestamp_validity_window, }; let states_iter = execution_state.into_states_iter(); diff --git a/programs/amm/src/tests.rs b/programs/amm/src/tests.rs index f0d53c96..14638f9d 100644 --- a/programs/amm/src/tests.rs +++ b/programs/amm/src/tests.rs @@ -2733,7 +2733,7 @@ fn simple_amm_remove() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -2814,7 +2814,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -2898,7 +2898,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -2971,7 +2971,7 @@ fn simple_amm_new_definition_uninitialized_pool() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -3033,7 +3033,7 @@ fn simple_amm_add() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -3090,7 +3090,7 @@ fn simple_amm_swap_1() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -3140,7 +3140,7 @@ fn simple_amm_swap_2() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1).unwrap(); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 545c63fa..16667051 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -16,6 +16,7 @@ use mempool::{MemPool, MemPoolHandle}; #[cfg(feature = "mock")] pub use mock::SequencerCoreWithMockClients; use nssa::V03State; +use nssa_core::{BlockId, Timestamp}; pub use storage::error::DbError; use testnet_initial_state::initial_state; @@ -165,14 +166,16 @@ impl SequencerCore Result { match &tx { NSSATransaction::Public(tx) => self .state - .transition_from_public_transaction(tx, self.next_block_id()), + .transition_from_public_transaction(tx, block_id, timestamp), NSSATransaction::PrivacyPreserving(tx) => self .state - .transition_from_privacy_preserving_transaction(tx, self.next_block_id()), + .transition_from_privacy_preserving_transaction(tx, block_id, timestamp), NSSATransaction::ProgramDeployment(tx) => self .state .transition_from_program_deployment_transaction(tx), @@ -218,7 +221,7 @@ impl SequencerCore SequencerCore SequencerCore { valid_transactions.push(valid_tx); @@ -272,7 +276,7 @@ impl SequencerCore(); @@ -24,6 +25,7 @@ fn main() { vec![pre], vec![AccountPostState::new(post)], ) - .with_validity_window(validity_window) + .with_block_validity_window(block_validity_window) + .with_timestamp_validity_window(timestamp_validity_window) .write(); } diff --git a/test_program_methods/guest/src/bin/validity_window_chain_caller.rs b/test_program_methods/guest/src/bin/validity_window_chain_caller.rs index cbd110dd..39f8ad69 100644 --- a/test_program_methods/guest/src/bin/validity_window_chain_caller.rs +++ b/test_program_methods/guest/src/bin/validity_window_chain_caller.rs @@ -1,21 +1,23 @@ use nssa_core::program::{ - AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, ValidityWindow, - read_nssa_inputs, + AccountPostState, BlockValidityWindow, ChainedCall, ProgramId, ProgramInput, ProgramOutput, + TimestampValidityWindow, read_nssa_inputs, }; use risc0_zkvm::serde::to_vec; -/// A program that sets a validity window on its output and chains to another program with a -/// potentially different validity window. +/// A program that sets a block validity window on its output and chains to another program with a +/// potentially different block validity window. /// /// Instruction: (`window`, `chained_program_id`, `chained_window`) /// The initial output uses `window` and chains to `chained_program_id` with `chained_window`. -type Instruction = (ValidityWindow, ProgramId, ValidityWindow); +/// The chained program (`validity_window`) expects `(BlockValidityWindow, TimestampValidityWindow)` +/// so an unbounded timestamp window is appended automatically. +type Instruction = (BlockValidityWindow, ProgramId, BlockValidityWindow); fn main() { let ( ProgramInput { pre_states, - instruction: (validity_window, chained_program_id, chained_validity_window), + instruction: (block_validity_window, chained_program_id, chained_block_validity_window), }, instruction_words, ) = read_nssa_inputs::(); @@ -23,7 +25,11 @@ fn main() { let [pre] = <[_; 1]>::try_from(pre_states.clone()).expect("Expected exactly one pre state"); let post = pre.account.clone(); - let chained_instruction = to_vec(&chained_validity_window).unwrap(); + let chained_instruction = to_vec(&( + chained_block_validity_window, + TimestampValidityWindow::new_unbounded(), + )) + .unwrap(); let chained_call = ChainedCall { program_id: chained_program_id, instruction_data: chained_instruction, @@ -36,7 +42,7 @@ fn main() { vec![pre], vec![AccountPostState::new(post)], ) - .with_validity_window(validity_window) + .with_block_validity_window(block_validity_window) .with_chained_calls(vec![chained_call]) .write(); }