diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 93553a8..72c4fe8 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -14,6 +14,7 @@ secp256k1 = "0.31.1" rand = "0.8" borsh = "1.5.7" hex = "0.4.3" +risc0-binfmt = "3.0.2" [dev-dependencies] test-program-methods = { path = "test_program_methods" } diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 0e85789..8ed9657 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -48,4 +48,10 @@ pub enum NssaError { #[error("Circuit proving error")] CircuitProvingError(String), + + #[error("Invalid program bytecode")] + InvalidProgramBytecode, + + #[error("Program already exists")] + ProgramAlreadyExists, } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index b7e041d..b394cad 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -3,8 +3,8 @@ pub mod error; mod merkle_tree; pub mod privacy_preserving_transaction; pub mod program; -pub mod public_transaction; pub mod program_deployment_transaction; +pub mod public_transaction; mod signature; mod state; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index cdf02c2..af4ba39 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -29,7 +29,7 @@ impl PrivacyPreservingTransaction { pub(crate) fn validate_and_produce_public_state_diff( &self, - state: &mut V01State, + state: &V01State, ) -> Result, NssaError> { let message = &self.message; let witness_set = &self.witness_set; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 5abc153..17f6d2b 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -2,10 +2,7 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; -use program_methods::{ - AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, - TOKEN_ID, -}; +use program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; @@ -14,16 +11,26 @@ use crate::error::NssaError; #[derive(Debug, PartialEq, Eq)] pub struct Program { id: ProgramId, - elf: &'static [u8], + elf: Vec, } impl Program { + pub(crate) fn new(bytecode: Vec) -> Result { + let binary = risc0_binfmt::ProgramBinary::decode(&bytecode) + .map_err(|_| NssaError::InvalidProgramBytecode)?; + let id = binary + .compute_image_id() + .map_err(|_| NssaError::InvalidProgramBytecode)? + .into(); + Ok(Self { elf: bytecode, id }) + } + pub fn id(&self) -> ProgramId { self.id } - pub(crate) fn elf(&self) -> &'static [u8] { - self.elf + pub(crate) fn elf(&self) -> &[u8] { + &self.elf } pub fn serialize_instruction( @@ -45,7 +52,7 @@ impl Program { // Execute the program (without proving) let executor = default_executor(); let session_info = executor - .execute(env, self.elf) + .execute(env, self.elf()) .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; // Get outputs @@ -71,33 +78,34 @@ impl Program { } pub fn authenticated_transfer_program() -> Self { - Self { - id: AUTHENTICATED_TRANSFER_ID, - elf: AUTHENTICATED_TRANSFER_ELF, - } + // This unwrap won't panic since the `AUTHENTICATED_TRANSFER_ELF` comes from risc0 build of + // `program_methods` + Self::new(AUTHENTICATED_TRANSFER_ELF.to_vec()).unwrap() } pub fn token() -> Self { - Self { - id: TOKEN_ID, - elf: TOKEN_ELF, - } + // This unwrap won't panic since the `TOKEN_ELF` comes from risc0 build of + // `program_methods` + Self::new(TOKEN_ELF.to_vec()).unwrap() } } // TODO: Testnet only. Refactor to prevent compilation on mainnet. impl Program { pub fn pinata() -> Self { - Self { - id: PINATA_ID, - elf: PINATA_ELF, - } + // This unwrap won't panic since the `PINATA_ELF` comes from risc0 build of + // `program_methods` + Self::new(PINATA_ELF.to_vec()).unwrap() } } #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; + use program_methods::{ + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, + TOKEN_ID, + }; use crate::program::Program; @@ -108,7 +116,7 @@ mod tests { Program { id: NONCE_CHANGER_ID, - elf: NONCE_CHANGER_ELF, + elf: NONCE_CHANGER_ELF.to_vec(), } } @@ -118,7 +126,7 @@ mod tests { Program { id: EXTRA_OUTPUT_ID, - elf: EXTRA_OUTPUT_ELF, + elf: EXTRA_OUTPUT_ELF.to_vec(), } } @@ -128,7 +136,7 @@ mod tests { Program { id: MISSING_OUTPUT_ID, - elf: MISSING_OUTPUT_ELF, + elf: MISSING_OUTPUT_ELF.to_vec(), } } @@ -138,7 +146,7 @@ mod tests { Program { id: PROGRAM_OWNER_CHANGER_ID, - elf: PROGRAM_OWNER_CHANGER_ELF, + elf: PROGRAM_OWNER_CHANGER_ELF.to_vec(), } } @@ -148,7 +156,7 @@ mod tests { Program { id: SIMPLE_BALANCE_TRANSFER_ID, - elf: SIMPLE_BALANCE_TRANSFER_ELF, + elf: SIMPLE_BALANCE_TRANSFER_ELF.to_vec(), } } @@ -158,7 +166,7 @@ mod tests { Program { id: DATA_CHANGER_ID, - elf: DATA_CHANGER_ELF, + elf: DATA_CHANGER_ELF.to_vec(), } } @@ -168,7 +176,7 @@ mod tests { Program { id: MINTER_ID, - elf: MINTER_ELF, + elf: MINTER_ELF.to_vec(), } } @@ -178,7 +186,7 @@ mod tests { Program { id: BURNER_ID, - elf: BURNER_ELF, + elf: BURNER_ELF.to_vec(), } } } @@ -216,4 +224,18 @@ mod tests { assert_eq!(sender_post, expected_sender_post); assert_eq!(recipient_post, expected_recipient_post); } + + #[test] + fn test_builtin_programs() { + let auth_transfer_program = Program::authenticated_transfer_program(); + let token_program = Program::token(); + let pinata_program = Program::pinata(); + + assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID); + assert_eq!(auth_transfer_program.elf, AUTHENTICATED_TRANSFER_ELF); + assert_eq!(token_program.id, TOKEN_ID); + assert_eq!(token_program.elf, TOKEN_ELF); + assert_eq!(pinata_program.id, PINATA_ID); + assert_eq!(pinata_program.elf, PINATA_ELF); + } } diff --git a/nssa/src/program_deployment_transaction/mod.rs b/nssa/src/program_deployment_transaction/mod.rs index 6aa1cda..0bf8c7f 100644 --- a/nssa/src/program_deployment_transaction/mod.rs +++ b/nssa/src/program_deployment_transaction/mod.rs @@ -1,2 +1,4 @@ mod message; mod transaction; + +pub use transaction::ProgramDeploymentTransaction; diff --git a/nssa/src/program_deployment_transaction/transaction.rs b/nssa/src/program_deployment_transaction/transaction.rs index 36f3de0..dfd348b 100644 --- a/nssa/src/program_deployment_transaction/transaction.rs +++ b/nssa/src/program_deployment_transaction/transaction.rs @@ -1,8 +1,6 @@ -use std::collections::HashMap; - -use nssa_core::{account::Account, address::Address}; - -use crate::{V01State, error::NssaError, program_deployment_transaction::message::Message}; +use crate::{ + V01State, error::NssaError, program::Program, program_deployment_transaction::message::Message, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProgramDeploymentTransaction { @@ -13,10 +11,17 @@ impl ProgramDeploymentTransaction { pub fn new(message: Message) -> Self { Self { message } } + pub(crate) fn validate_and_produce_public_state_diff( &self, - state: &mut V01State, - ) -> Result, NssaError> { - todo!() + state: &V01State, + ) -> Result { + // TODO: remove clone + let program = Program::new(self.message.bytecode.clone())?; + if state.programs().contains_key(&program.id()) { + Err(NssaError::ProgramAlreadyExists) + } else { + Ok(program) + } } } diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index d6b9614..92db97e 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -102,7 +102,7 @@ impl PublicTransaction { // Check the `program_id` corresponds to a built-in program // Only allowed program so far is the authenticated transfer program - let Some(program) = state.builtin_programs().get(&message.program_id) else { + let Some(program) = state.programs().get(&message.program_id) else { return Err(NssaError::InvalidInput("Unknown program".into())); }; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index b93794c..1819b50 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1,6 +1,7 @@ use crate::{ error::NssaError, merkle_tree::MerkleTree, privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program, + program_deployment_transaction::ProgramDeploymentTransaction, public_transaction::PublicTransaction, }; use nssa_core::{ @@ -157,6 +158,15 @@ impl V01State { Ok(()) } + pub fn transition_from_program_deployment_transaction( + &mut self, + tx: &ProgramDeploymentTransaction, + ) -> Result<(), NssaError> { + let program = tx.validate_and_produce_public_state_diff(self)?; + self.insert_program(program); + Ok(()) + } + fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account { self.public_state.entry(address).or_default() } @@ -172,7 +182,7 @@ impl V01State { self.private_state.0.get_proof_for(commitment) } - pub(crate) fn builtin_programs(&self) -> &HashMap { + pub(crate) fn programs(&self) -> &HashMap { &self.programs } @@ -355,7 +365,7 @@ pub mod tests { fn test_builtin_programs_getter() { let state = V01State::new_with_genesis_accounts(&[], &[]); - let builtin_programs = state.builtin_programs(); + let builtin_programs = state.programs(); assert_eq!(builtin_programs, &state.programs); }