diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 4c4a4eae..b57179f5 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index ec04613e..7786005e 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/clock.bin b/artifacts/program_methods/clock.bin new file mode 100644 index 00000000..c3f8af15 Binary files /dev/null and b/artifacts/program_methods/clock.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index 25b90260..d23e1fe6 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 b7da464a..788a9b59 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 0c26e6a2..d5cb051f 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 d708a10f..896c5ec8 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 2c162dfc..89a0fba7 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 79826a4f..3303f4fc 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 91240905..36152765 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 e34786fe..e739591a 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 f44c9045..ea955433 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 7f7612a3..655c885d 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 8798992a..777cdaf1 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 161e427c..70e5e6f1 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 20ce3ac4..39669da3 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 5ff38952..ff6029df 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 9f63259f..b501d9fa 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 36efd493..130cb2e1 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 7acb622b..c5c7829f 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 6177443f..b9788803 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 ce2f40bc..cb661d53 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 new file mode 100644 index 00000000..06f253ea Binary files /dev/null and b/artifacts/test_program_methods/validity_window_chain_caller.bin differ diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 182d8960..d57e4d2f 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -205,14 +205,16 @@ impl ValidityWindow { self.to } - /// Sets the inclusive lower bound. Returns `Err` if the updated window would be empty or inverted. + /// Sets the inclusive lower bound. Returns `Err` if the updated window would be empty or + /// inverted. pub fn set_from(&mut self, id: Option) -> Result<(), InvalidWindow> { let prev = self.from; self.from = id; self.check_window().inspect_err(|_| self.from = prev) } - /// Sets the exclusive upper bound. Returns `Err` if the updated window would be empty or inverted. + /// Sets the exclusive upper bound. Returns `Err` if the updated window would be empty or + /// inverted. pub fn set_to(&mut self, id: Option) -> Result<(), InvalidWindow> { let prev = self.to; self.to = id; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 2ab141a3..0ae7eaac 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -174,12 +174,13 @@ mod tests { #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] use nssa_core::{ - Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, + Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, }; use super::*; use crate::{ + error::NssaError, privacy_preserving_transaction::circuit::execute_and_prove, program::Program, state::{ @@ -364,4 +365,46 @@ mod tests { .unwrap(); assert_eq!(recipient_post, expected_private_account_2); } + + #[test] + fn circuit_fails_when_chained_validity_windows_have_empty_intersection() { + let account_keys = test_private_account_keys_1(); + let pre = AccountWithMetadata::new( + Account::default(), + false, + AccountId::from(&account_keys.npk()), + ); + + let validity_window_chain_caller = Program::validity_window_chain_caller(); + let validity_window = Program::validity_window(); + + let instruction = Program::serialize_instruction(( + Some(1_u64), + Some(4_u64), + validity_window.id(), + Some(4_u64), + Some(7_u64), + )) + .unwrap(); + + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk()); + + let program_with_deps = ProgramWithDependencies::new( + validity_window_chain_caller, + [(validity_window.id(), validity_window)].into(), + ); + + let result = execute_and_prove( + vec![pre], + instruction, + vec![2], + vec![(account_keys.npk(), shared_secret)], + vec![], + vec![None], + &program_with_deps, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } diff --git a/nssa/src/program.rs b/nssa/src/program.rs index fa5e7b42..7750e20b 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -292,6 +292,12 @@ mod tests { // `program_methods` Self::new(VALIDITY_WINDOW_ELF.to_vec()).unwrap() } + + #[must_use] + pub fn validity_window_chain_caller() -> Self { + use test_program_methods::VALIDITY_WINDOW_CHAIN_CALLER_ELF; + Self::new(VALIDITY_WINDOW_CHAIN_CALLER_ELF.to_vec()).unwrap() + } } #[test] diff --git a/test_program_methods/guest/src/bin/burner.rs b/test_program_methods/guest/src/bin/burner.rs index a6a514c8..991091c0 100644 --- a/test_program_methods/guest/src/bin/burner.rs +++ b/test_program_methods/guest/src/bin/burner.rs @@ -19,6 +19,10 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance = account_post.balance.saturating_sub(balance_to_burn); - ProgramOutput::new(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]) - .write(); + ProgramOutput::new( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/minter.rs b/test_program_methods/guest/src/bin/minter.rs index 914e55e0..ac29e4d3 100644 --- a/test_program_methods/guest/src/bin/minter.rs +++ b/test_program_methods/guest/src/bin/minter.rs @@ -16,6 +16,10 @@ fn main() { .checked_add(1) .expect("Balance overflow"); - ProgramOutput::new(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]) - .write(); + ProgramOutput::new( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/nonce_changer.rs b/test_program_methods/guest/src/bin/nonce_changer.rs index 295c244a..0cecdc81 100644 --- a/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/test_program_methods/guest/src/bin/nonce_changer.rs @@ -13,6 +13,10 @@ fn main() { let mut account_post = account_pre.clone(); account_post.nonce.public_account_nonce_increment(); - ProgramOutput::new(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]) - .write(); + ProgramOutput::new( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/program_owner_changer.rs b/test_program_methods/guest/src/bin/program_owner_changer.rs index a435524e..7e421351 100644 --- a/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -13,6 +13,10 @@ fn main() { let mut account_post = account_pre.clone(); account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7]; - ProgramOutput::new(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]) - .write(); + ProgramOutput::new( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ) + .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 new file mode 100644 index 00000000..61534207 --- /dev/null +++ b/test_program_methods/guest/src/bin/validity_window_chain_caller.rs @@ -0,0 +1,53 @@ +use nssa_core::program::{ + AccountPostState, BlockId, ChainedCall, ProgramId, ProgramInput, ProgramOutput, + 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. +/// +/// Instruction: (from_id, until_id, chained_program_id, chained_from, chained_until) +/// The initial output uses [from_id, until_id) and chains to `chained_program_id` with +/// [chained_from, chained_until). +type Instruction = ( + Option, + Option, + ProgramId, + Option, + Option, +); + +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (from_id, until_id, chained_program_id, chained_from, chained_until), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [pre] = <[_; 1]>::try_from(pre_states.clone()) + .unwrap_or_else(|_| panic!("Expected exactly one pre state")); + let post = pre.account.clone(); + + let chained_instruction = to_vec(&(chained_from, chained_until)).unwrap(); + let chained_call = ChainedCall { + program_id: chained_program_id, + instruction_data: chained_instruction, + pre_states, + pda_seeds: vec![], + }; + + ProgramOutput::new( + instruction_words, + vec![pre], + vec![AccountPostState::new(post)], + ) + .valid_from_id(from_id) + .unwrap() + .valid_until_id(until_id) + .unwrap() + .with_chained_calls(vec![chained_call]) + .write(); +}