From caf74b8346bd89386bb679e65329a9a147575b98 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 28 Mar 2026 01:13:48 -0300 Subject: [PATCH] refactor validity window with generic --- .../src/pages/transaction_page.rs | 11 +- indexer/service/protocol/src/convert.rs | 27 +- indexer/service/protocol/src/lib.rs | 20 +- indexer/service/src/mock_service.rs | 3 +- nssa/core/src/circuit_io.rs | 8 +- nssa/core/src/program.rs | 244 ++++++++---------- .../privacy_preserving_transaction/message.rs | 15 +- .../transaction.rs | 13 +- nssa/src/public_transaction/transaction.rs | 5 +- nssa/src/state.rs | 4 +- .../src/bin/privacy_preserving_circuit.rs | 39 +-- .../guest/src/bin/validity_window.rs | 8 +- .../src/bin/validity_window_chain_caller.rs | 14 +- 13 files changed, 195 insertions(+), 216 deletions(-) diff --git a/explorer_service/src/pages/transaction_page.rs b/explorer_service/src/pages/transaction_page.rs index ed3d8aac..80ab2f45 100644 --- a/explorer_service/src/pages/transaction_page.rs +++ b/explorer_service/src/pages/transaction_page.rs @@ -177,7 +177,8 @@ 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: _, @@ -214,8 +215,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/service/protocol/src/convert.rs b/indexer/service/protocol/src/convert.rs index 14117a48..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,18 +698,13 @@ impl From for common::HashType { // ValidityWindow conversions // ============================================================================ -impl From for ValidityWindow { - fn from(value: nssa_core::program::ValidityWindow) -> Self { - Self(( - value.start(), - value.end(), - value.from_timestamp(), - value.to_timestamp(), - )) +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 51119260..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)] @@ -302,22 +303,15 @@ pub struct Nullifier( ); #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] -pub struct ValidityWindow( - pub ( - Option, - Option, - Option, - Option, - ), -); +pub struct ValidityWindow(pub (Option, Option)); impl Display for ValidityWindow { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0 { - (Some(start), Some(end), ..) => write!(f, "[{start}, {end})"), - (Some(start), None, ..) => write!(f, "[{start}, \u{221e})"), - (None, Some(end), ..) => write!(f, "(-\u{221e}, {end})"), - (None, None, ..) => write!(f, "(-\u{221e}, \u{221e})"), + (Some(start), Some(end)) => write!(f, "[{start}, {end})"), + (Some(start), None) => write!(f, "[{start}, \u{221e})"), + (None, Some(end)) => write!(f, "(-\u{221e}, {end})"), + (None, None) => write!(f, "(-\u{221e}, \u{221e})"), } } } diff --git a/indexer/service/src/mock_service.rs b/indexer/service/src/mock_service.rs index 39d46269..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, 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..dc6e7723 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::{BlockId, ProgramId, ProgramOutput, Timestamp, ValidityWindow}, }; #[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: ValidityWindow, + pub timestamp_validity_window: ValidityWindow, } #[cfg(feature = "host")] @@ -102,7 +103,8 @@ mod tests { ), [0xab; 32], )], - validity_window: (Some(1), None).try_into().unwrap(), + block_validity_window: (Some(1u64), None).try_into().unwrap(), + timestamp_validity_window: ValidityWindow::new_unbounded(), }; let bytes = output.to_bytes(); let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 095d1777..f60ec9bf 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -162,155 +162,93 @@ pub type Timestamp = u64; any(feature = "host", test), derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize) )] -pub struct ValidityWindow { - from: Option, - to: Option, - from_timestamp: Option, - to_timestamp: 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 { from: None, to: None, - from_timestamp: None, - to_timestamp: None, } } +} - /// Valid for block IDs in the range [from, to) and timestamps in [`from_timestamp`, - /// `to_timestamp`). +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(&self, block_id: BlockId, timestamp_ms: Timestamp) -> bool { - self.from.is_none_or(|start| block_id >= start) - && self.to.is_none_or(|end| block_id < end) - && self.from_timestamp.is_none_or(|t| timestamp_ms >= t) - && self.to_timestamp.is_none_or(|t| timestamp_ms < t) - } - - /// Valid for block IDs in the range [from, to), where `from` is included and `to` is excluded. - /// Ignores timestamp bounds. - #[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 - { - return Err(InvalidWindow); - } - if let (Some(from_ts), Some(until_ts)) = (self.from_timestamp, self.to_timestamp) - && from_ts >= until_ts + fn check_window(&self) -> Result<(), InvalidWindow> { + if let (Some(from), Some(to)) = (self.from, self.to) + && from >= to { 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 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 fn end(&self) -> Option { self.to } - - #[must_use] - pub const fn from_timestamp(&self) -> Option { - self.from_timestamp - } - - #[must_use] - pub const fn to_timestamp(&self) -> Option { - self.to_timestamp - } } -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, - from_timestamp: None, - to_timestamp: None, }; this.check_window()?; Ok(this) } } -impl - TryFrom<( - Option, - Option, - Option, - Option, - )> for ValidityWindow -{ +impl TryFrom> for ValidityWindow { type Error = InvalidWindow; - fn try_from( - value: ( - Option, - Option, - Option, - Option, - ), - ) -> Result { - let this = Self { - from: value.0, - to: value.1, - from_timestamp: value.2, - to_timestamp: value.3, - }; - this.check_window()?; - Ok(this) - } -} - -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, - from_timestamp: None, - to_timestamp: None, } } } -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), - from_timestamp: None, - to_timestamp: None, } } } -impl From for ValidityWindow { +impl From for ValidityWindow { fn from(_: std::ops::RangeFull) -> Self { Self::new_unbounded() } @@ -332,8 +270,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: ValidityWindow, + /// The timestamp window where the program output is valid. + pub timestamp_validity_window: ValidityWindow, } impl ProgramOutput { @@ -347,7 +287,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(), } } @@ -360,31 +301,45 @@ 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, Error = InvalidWindow>>( 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, Error = InvalidWindow>>( + 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.validity_window.from_timestamp = ts; - self.validity_window.check_window()?; + 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.validity_window.to_timestamp = ts; - self.validity_window.check_window()?; + self.timestamp_validity_window = (self.timestamp_validity_window.start(), ts).try_into()?; Ok(self) } } @@ -541,128 +496,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..b59cdcd3 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::{BlockId, Timestamp, ValidityWindow}, }; 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: ValidityWindow, + pub timestamp_validity_window: ValidityWindow, } 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::ValidityWindow, }; 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: ValidityWindow::new_unbounded(), + timestamp_validity_window: ValidityWindow::new_unbounded(), } } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 8823e364..2baff0c5 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -95,7 +95,9 @@ impl PrivacyPreservingTransaction { } // Verify validity window - if !message.validity_window.is_valid_for(block_id, timestamp_ms) { + if !message.block_validity_window.is_valid_for(block_id) + || !message.timestamp_validity_window.is_valid_for(timestamp_ms) + { return Err(NssaError::OutOfValidityWindow); } @@ -120,7 +122,8 @@ impl PrivacyPreservingTransaction { &message.encrypted_private_post_states, &message.new_commitments, &message.new_nullifiers, - &message.validity_window, + &message.block_validity_window, + &message.timestamp_validity_window, )?; // 5. Commitment freshness @@ -182,7 +185,8 @@ fn check_privacy_preserving_circuit_proof_is_valid( encrypted_private_post_states: &[EncryptedAccountData], new_commitments: &[Commitment], new_nullifiers: &[(Nullifier, CommitmentSetDigest)], - validity_window: &ValidityWindow, + block_validity_window: &ValidityWindow, + timestamp_validity_window: &ValidityWindow, ) -> Result<(), NssaError> { let output = PrivacyPreservingCircuitOutput { public_pre_states: public_pre_states.to_vec(), @@ -194,7 +198,8 @@ fn check_privacy_preserving_circuit_proof_is_valid( .collect(), new_commitments: new_commitments.to_vec(), new_nullifiers: new_nullifiers.to_vec(), - validity_window: validity_window.to_owned(), + block_validity_window: block_validity_window.to_owned(), + timestamp_validity_window: timestamp_validity_window.to_owned(), }; proof .is_valid_for(&output) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 9f93805e..9bb27e04 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -194,9 +194,8 @@ impl PublicTransaction { // Verify validity window ensure!( - program_output - .validity_window - .is_valid_for(block_id, timestamp_ms), + program_output.block_validity_window.is_valid_for(block_id) + && program_output.timestamp_validity_window.is_valid_for(timestamp_ms), NssaError::OutOfValidityWindow ); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 6a2681b8..e250356c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -3021,7 +3021,7 @@ pub mod tests { validity_window: (Option, Option), block_id: BlockId, ) { - let validity_window: ValidityWindow = validity_window.try_into().unwrap(); + let validity_window: ValidityWindow = 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()); @@ -3068,7 +3068,7 @@ pub mod tests { validity_window: (Option, Option), block_id: BlockId, ) { - let validity_window: ValidityWindow = validity_window.try_into().unwrap(); + let validity_window: ValidityWindow = 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()); diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 2a60989d..3e43410b 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -10,8 +10,8 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Nonce}, compute_digest_for_path, program::{ - AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId, - ProgramOutput, ValidityWindow, validate_execution, + AccountPostState, BlockId, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, + ProgramId, ProgramOutput, Timestamp, ValidityWindow, validate_execution, }, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -20,39 +20,47 @@ use risc0_zkvm::{guest::env, serde::to_vec}; struct ExecutionState { pre_states: Vec, post_states: HashMap, - validity_window: ValidityWindow, + block_validity_window: ValidityWindow, + timestamp_validity_window: ValidityWindow, } impl ExecutionState { /// Validate program outputs and derive the overall execution state. pub fn derive_from_outputs(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 valid_from_ts = program_outputs + let ts_valid_from = program_outputs .iter() - .filter_map(|output| output.validity_window.from_timestamp()) + .filter_map(|output| output.timestamp_validity_window.start()) .max(); - let valid_until_ts = program_outputs + let ts_valid_until = program_outputs .iter() - .filter_map(|output| output.validity_window.to_timestamp()) + .filter_map(|output| output.timestamp_validity_window.end()) .min(); - let validity_window = (valid_from_id, valid_until_id, valid_from_ts, valid_until_ts) + let block_validity_window: ValidityWindow = (block_valid_from, block_valid_until) .try_into() .expect( - "There should be non empty intersection in the program output validity windows", + "There should be non empty intersection in the program output block validity windows", ); + let timestamp_validity_window: ValidityWindow = + (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 { @@ -235,7 +243,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/test_program_methods/guest/src/bin/validity_window.rs b/test_program_methods/guest/src/bin/validity_window.rs index 00e8e5e8..d2747401 100644 --- a/test_program_methods/guest/src/bin/validity_window.rs +++ b/test_program_methods/guest/src/bin/validity_window.rs @@ -1,14 +1,14 @@ use nssa_core::program::{ - AccountPostState, ProgramInput, ProgramOutput, ValidityWindow, read_nssa_inputs, + AccountPostState, BlockId, ProgramInput, ProgramOutput, ValidityWindow, read_nssa_inputs, }; -type Instruction = ValidityWindow; +type Instruction = ValidityWindow; fn main() { let ( ProgramInput { pre_states, - instruction: validity_window, + instruction: block_validity_window, }, instruction_words, ) = read_nssa_inputs::(); @@ -24,6 +24,6 @@ fn main() { vec![pre], vec![AccountPostState::new(post)], ) - .with_validity_window(validity_window) + .with_block_validity_window(block_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..796469cd 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,21 @@ use nssa_core::program::{ - AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, ValidityWindow, + AccountPostState, BlockId, ChainedCall, ProgramId, ProgramInput, ProgramOutput, ValidityWindow, 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); +type Instruction = (ValidityWindow, ProgramId, ValidityWindow); 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 +23,7 @@ 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).unwrap(); let chained_call = ChainedCall { program_id: chained_program_id, instruction_data: chained_instruction, @@ -36,7 +36,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(); }