refactor validity window with generic

This commit is contained in:
Sergio Chouhy 2026-03-28 01:13:48 -03:00
parent 4746548994
commit caf74b8346
13 changed files with 195 additions and 216 deletions

View File

@ -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 {
<span class="info-value">{format!("{proof_len} bytes")}</span>
</div>
<div class="info-row">
<span class="info-label">"Validity Window:"</span>
<span class="info-value">{validity_window.to_string()}</span>
<span class="info-label">"Block Validity Window:"</span>
<span class="info-value">{block_validity_window.to_string()}</span>
</div>
<div class="info-row">
<span class="info-label">"Timestamp Validity Window:"</span>
<span class="info-value">{timestamp_validity_window.to_string()}</span>
</div>
</div>

View File

@ -287,7 +287,8 @@ impl From<nssa::privacy_preserving_transaction::message::Message> 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<nssa::privacy_preserving_transaction::message::Message> 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<PrivacyPreservingMessage> 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<PrivacyPreservingMessage> 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<HashType> for common::HashType {
// ValidityWindow conversions
// ============================================================================
impl From<nssa_core::program::ValidityWindow> for ValidityWindow {
fn from(value: nssa_core::program::ValidityWindow) -> Self {
Self((
value.start(),
value.end(),
value.from_timestamp(),
value.to_timestamp(),
))
impl From<nssa_core::program::ValidityWindow<u64>> for ValidityWindow {
fn from(value: nssa_core::program::ValidityWindow<u64>) -> Self {
Self((value.start(), value.end()))
}
}
impl TryFrom<ValidityWindow> for nssa_core::program::ValidityWindow {
impl TryFrom<ValidityWindow> for nssa_core::program::ValidityWindow<u64> {
type Error = nssa_core::program::InvalidWindow;
fn try_from(value: ValidityWindow) -> Result<Self, Self::Error> {

View File

@ -235,7 +235,8 @@ pub struct PrivacyPreservingMessage {
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
pub new_commitments: Vec<Commitment>,
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<BlockId>,
Option<BlockId>,
Option<TimeStamp>,
Option<TimeStamp>,
),
);
pub struct ValidityWindow(pub (Option<BlockId>, Option<BlockId>));
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})"),
}
}
}

View File

@ -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![],

View File

@ -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<Ciphertext>,
pub new_commitments: Vec<Commitment>,
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
pub validity_window: ValidityWindow,
pub block_validity_window: ValidityWindow<BlockId>,
pub timestamp_validity_window: ValidityWindow<Timestamp>,
}
#[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();

View File

@ -162,155 +162,93 @@ pub type Timestamp = u64;
any(feature = "host", test),
derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)
)]
pub struct ValidityWindow {
from: Option<BlockId>,
to: Option<BlockId>,
from_timestamp: Option<Timestamp>,
to_timestamp: Option<Timestamp>,
pub struct ValidityWindow<T> {
from: Option<T>,
to: Option<T>,
}
impl ValidityWindow {
/// Creates a window with no bounds, valid for every block ID.
impl<T> ValidityWindow<T> {
/// 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<T: Copy + PartialOrd> ValidityWindow<T> {
/// 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<BlockId> {
pub fn start(&self) -> Option<T> {
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<BlockId> {
pub fn end(&self) -> Option<T> {
self.to
}
#[must_use]
pub const fn from_timestamp(&self) -> Option<Timestamp> {
self.from_timestamp
}
#[must_use]
pub const fn to_timestamp(&self) -> Option<Timestamp> {
self.to_timestamp
}
}
impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
impl<T: Copy + PartialOrd> TryFrom<(Option<T>, Option<T>)> for ValidityWindow<T> {
type Error = InvalidWindow;
fn try_from(value: (Option<BlockId>, Option<BlockId>)) -> Result<Self, Self::Error> {
fn try_from(value: (Option<T>, Option<T>)) -> Result<Self, Self::Error> {
let this = Self {
from: value.0,
to: value.1,
from_timestamp: None,
to_timestamp: None,
};
this.check_window()?;
Ok(this)
}
}
impl
TryFrom<(
Option<BlockId>,
Option<BlockId>,
Option<Timestamp>,
Option<Timestamp>,
)> for ValidityWindow
{
impl<T: Copy + PartialOrd> TryFrom<std::ops::Range<T>> for ValidityWindow<T> {
type Error = InvalidWindow;
fn try_from(
value: (
Option<BlockId>,
Option<BlockId>,
Option<Timestamp>,
Option<Timestamp>,
),
) -> Result<Self, Self::Error> {
let this = Self {
from: value.0,
to: value.1,
from_timestamp: value.2,
to_timestamp: value.3,
};
this.check_window()?;
Ok(this)
}
}
impl TryFrom<std::ops::Range<BlockId>> for ValidityWindow {
type Error = InvalidWindow;
fn try_from(value: std::ops::Range<BlockId>) -> Result<Self, Self::Error> {
fn try_from(value: std::ops::Range<T>) -> Result<Self, Self::Error> {
(Some(value.start), Some(value.end)).try_into()
}
}
impl From<std::ops::RangeFrom<BlockId>> for ValidityWindow {
fn from(value: std::ops::RangeFrom<BlockId>) -> Self {
impl<T: Copy + PartialOrd> From<std::ops::RangeFrom<T>> for ValidityWindow<T> {
fn from(value: std::ops::RangeFrom<T>) -> Self {
Self {
from: Some(value.start),
to: None,
from_timestamp: None,
to_timestamp: None,
}
}
}
impl From<std::ops::RangeTo<BlockId>> for ValidityWindow {
fn from(value: std::ops::RangeTo<BlockId>) -> Self {
impl<T: Copy + PartialOrd> From<std::ops::RangeTo<T>> for ValidityWindow<T> {
fn from(value: std::ops::RangeTo<T>) -> Self {
Self {
from: None,
to: Some(value.end),
from_timestamp: None,
to_timestamp: None,
}
}
}
impl From<std::ops::RangeFull> for ValidityWindow {
impl<T> From<std::ops::RangeFull> for ValidityWindow<T> {
fn from(_: std::ops::RangeFull) -> Self {
Self::new_unbounded()
}
@ -332,8 +270,10 @@ pub struct ProgramOutput {
pub post_states: Vec<AccountPostState>,
/// The list of chained calls to other programs.
pub chained_calls: Vec<ChainedCall>,
/// 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<BlockId>,
/// The timestamp window where the program output is valid.
pub timestamp_validity_window: ValidityWindow<Timestamp>,
}
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<W: Into<ValidityWindow>>(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<W: Into<ValidityWindow<BlockId>>>(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<W: TryInto<ValidityWindow, Error = InvalidWindow>>(
pub fn try_with_block_validity_window<W: TryInto<ValidityWindow<BlockId>, Error = InvalidWindow>>(
mut self,
window: W,
) -> Result<Self, InvalidWindow> {
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<W: Into<ValidityWindow<Timestamp>>>(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<ValidityWindow<Timestamp>, Error = InvalidWindow>>(
mut self,
window: W,
) -> Result<Self, InvalidWindow> {
self.timestamp_validity_window = window.try_into()?;
Ok(self)
}
pub fn valid_from_timestamp(mut self, ts: Option<Timestamp>) -> Result<Self, InvalidWindow> {
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<Timestamp>) -> Result<Self, InvalidWindow> {
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<u64> = 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<u64> = (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<u64> = (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<u64> = (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::<u64>::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::<u64>::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<u64> = (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<u64> = 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<u64> = 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::<u64>::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::<u64>::try_from(from..to).is_err());
}
#[test]
fn validity_window_from_range_from() {
let w: ValidityWindow = (5_u64..).into();
let w: ValidityWindow<u64> = (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<u64> = (..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<u64> = (..).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());
}

View File

@ -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<EncryptedAccountData>,
pub new_commitments: Vec<Commitment>,
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
pub validity_window: ValidityWindow,
pub block_validity_window: ValidityWindow<BlockId>,
pub timestamp_validity_window: ValidityWindow<Timestamp>,
}
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(),
}
}

View File

@ -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<BlockId>,
timestamp_validity_window: &ValidityWindow<Timestamp>,
) -> 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)

View File

@ -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
);

View File

@ -3021,7 +3021,7 @@ pub mod tests {
validity_window: (Option<BlockId>, Option<BlockId>),
block_id: BlockId,
) {
let validity_window: ValidityWindow = validity_window.try_into().unwrap();
let validity_window: ValidityWindow<BlockId> = 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<BlockId>, Option<BlockId>),
block_id: BlockId,
) {
let validity_window: ValidityWindow = validity_window.try_into().unwrap();
let validity_window: ValidityWindow<BlockId> = 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());

View File

@ -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<AccountWithMetadata>,
post_states: HashMap<AccountId, Account>,
validity_window: ValidityWindow,
block_validity_window: ValidityWindow<BlockId>,
timestamp_validity_window: ValidityWindow<Timestamp>,
}
impl ExecutionState {
/// Validate program outputs and derive the overall execution state.
pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec<ProgramOutput>) -> 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<BlockId> = (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<Timestamp> =
(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();

View File

@ -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<BlockId>;
fn main() {
let (
ProgramInput {
pre_states,
instruction: validity_window,
instruction: block_validity_window,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
@ -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();
}

View File

@ -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<BlockId>, ProgramId, ValidityWindow<BlockId>);
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::<Instruction>();
@ -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();
}