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();
}