diff --git a/src/models/transaction.rs b/src/models/transaction.rs new file mode 100644 index 0000000..8eaec9b --- /dev/null +++ b/src/models/transaction.rs @@ -0,0 +1,162 @@ +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use thiserror::Error; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum AssetType { + Item, + // other asset types... +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Transaction { + pub id: String, // Transaction hash + pub genesis_hash: String, + pub asset_type: AssetType, + pub metadata: Option, + // other existing fields... +} + +#[derive(Debug, Error)] +pub enum TransactionError { + #[error("Invalid genesis hash for metadata update.")] + InvalidGenesisHash, + #[error("Asset type is not Item.")] + InvalidAssetType, + // other error variants... +} + +impl Transaction { + /// Creates a new Transaction. + pub fn new(id: String, genesis_hash: String, asset_type: AssetType, metadata: Option) -> Self { + Transaction { + id, + genesis_hash, + asset_type, + metadata, + } + } + + /// Updates the metadata of the transaction. + /// For item asset types, updates the genesis hash to the transaction's own hash. + pub fn update_metadata(&mut self, new_metadata: String) { + if let AssetType::Item = self.asset_type { + self.genesis_hash = self.id.clone(); + } + self.metadata = Some(new_metadata); + } + + /// Validates the transaction. + pub fn validate(&self, input_transaction: Option<&Transaction>) -> Result<(), TransactionError> { + match self.asset_type { + AssetType::Item => { + if let Some(input_tx) = input_transaction { + if self.genesis_hash != input_tx.id { + return Err(TransactionError::InvalidGenesisHash); + } + } else { + // For genesis transactions, the genesis_hash should be the same as id + if self.genesis_hash != self.id { + return Err(TransactionError::InvalidGenesisHash); + } + } + } + // Validate other asset types if needed + _ => {} + } + Ok(()) + } + + /// Computes the hash of the transaction. + pub fn compute_hash(&self) -> String { + let serialized = serde_json::to_string(&self).unwrap(); + let mut hasher = Sha256::new(); + hasher.update(serialized); + let result = hasher.finalize(); + hex::encode(result) + } + + /// Helper method to create a metadata update transaction. + pub fn create_metadata_update(original_tx: &Transaction, new_metadata: String) -> Self { + let mut updated_tx = original_tx.clone(); + updated_tx.update_metadata(new_metadata); + updated_tx.id = updated_tx.compute_hash(); + updated_tx + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_metadata_update() { + let original_tx = Transaction::new( + "original_tx_hash".to_string(), + "original_genesis_hash".to_string(), + AssetType::Item, + Some("Original metadata".to_string()), + ); + + let mut updated_tx = original_tx.clone(); + updated_tx.update_metadata("Updated metadata".to_string()); + + assert_eq!(updated_tx.metadata.unwrap(), "Updated metadata"); + assert_eq!(updated_tx.genesis_hash, updated_tx.id); + } + + #[test] + fn test_validation_success() { + let original_tx = Transaction::new( + "original_tx_hash".to_string(), + "original_genesis_hash".to_string(), + AssetType::Item, + Some("Original metadata".to_string()), + ); + + let updated_tx = Transaction::create_metadata_update(&original_tx, "Updated metadata".to_string()); + + let input_tx = original_tx.clone(); + assert!(updated_tx.validate(Some(&input_tx)).is_ok()); + } + + #[test] + fn test_validation_failure() { + let original_tx = Transaction::new( + "original_tx_hash".to_string(), + "original_genesis_hash".to_string(), + AssetType::Item, + Some("Original metadata".to_string()), + ); + + let mut updated_tx = Transaction::create_metadata_update(&original_tx, "Updated metadata".to_string()); + // Introduce invalid genesis hash + updated_tx.genesis_hash = "invalid_genesis_hash".to_string(); + + let input_tx = original_tx.clone(); + assert!(matches!( + updated_tx.validate(Some(&input_tx)), + Err(TransactionError::InvalidGenesisHash) + )); + } + + #[test] + fn test_genesis_transaction_validation() { + let genesis_tx = Transaction::new( + "genesis_tx_hash".to_string(), + "genesis_tx_hash".to_string(), + AssetType::Item, + Some("Genesis metadata".to_string()), + ); + + assert!(genesis_tx.validate(None).is_ok()); + + let mut invalid_genesis_tx = genesis_tx.clone(); + invalid_genesis_tx.genesis_hash = "wrong_genesis_hash".to_string(); + + assert!(matches!( + invalid_genesis_tx.validate(None), + Err(TransactionError::InvalidGenesisHash) + )); + } +} \ No newline at end of file diff --git a/src/script/interface_ops.rs b/src/script/interface_ops.rs index 7b9875b..c838600 100644 --- a/src/script/interface_ops.rs +++ b/src/script/interface_ops.rs @@ -5,6 +5,7 @@ use crate::crypto::sign_ed25519 as sign; use crate::crypto::sign_ed25519::{PublicKey, Signature}; use crate::primitives::asset::{Asset, TokenAmount}; use crate::primitives::transaction::*; +use crate::primitives::transaction::{Transaction}; use crate::script::lang::{ConditionStack, Script, Stack}; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; @@ -563,8 +564,6 @@ pub fn op_tuck(stack: &mut Stack) -> bool { true } -/*---- SPLICE OPS ----*/ - /// OP_CAT: Concatenates the two strings on top of the stack /// /// Example: OP_CAT([s1, s2]) -> [s1s2] @@ -770,8 +769,6 @@ pub fn op_size(stack: &mut Stack) -> bool { stack.push(StackEntry::Num(s.len())) } -/*---- BITWISE LOGIC OPS ----*/ - /// OP_INVERT: Computes bitwise NOT of the number on top of the stack /// /// Example: OP_INVERT([n]) -> [!n] @@ -965,547 +962,6 @@ pub fn op_equalverify(stack: &mut Stack) -> bool { true } -/*---- ARITHMETIC OPS ----*/ - -/// OP_1ADD: Adds ONE to the number on top of the stack -/// -/// Example: OP_1ADD([n]) -> [n+1] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_1add(stack: &mut Stack) -> bool { - let (op, desc) = (OP1ADD, OP1ADD_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n.checked_add(ONE) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_1SUB: Subtracts ONE from the number on top of the stack. -/// -/// Example: OP_1SUB([n]) -> [n-1] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_1sub(stack: &mut Stack) -> bool { - let (op, desc) = (OP1SUB, OP1SUB_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n.checked_sub(ONE) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_2MUL: Multiplies by TWO the number on top of the stack -/// -/// Example: OP_2MUL([n]) -> [n*2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_2mul(stack: &mut Stack) -> bool { - let (op, desc) = (OP2MUL, OP2MUL_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n.checked_mul(TWO) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_2DIV: Divides by TWO the number on top of the stack -/// -/// Example: OP_2DIV([n]) -> [n/2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_2div(stack: &mut Stack) -> bool { - let (op, desc) = (OP2DIV, OP2DIV_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - stack.push(StackEntry::Num(n / TWO)) -} - -/// OP_NOT: Substitutes the number on top of the stack with ONE if it is equal to ZERO, with ZERO otherwise -/// -/// Example: OP_NOT([n]) -> [1] if n == 0 -/// OP_NOT([n]) -> [0] if n != 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_not(stack: &mut Stack) -> bool { - let (op, desc) = (OPNOT, OPNOT_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n == ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - -/// OP_0NOTEQUAL: Substitutes the number on top of the stack with ONE if it is not equal to ZERO, with ZERO otherwise -/// -/// Example: OP_0NOTEQUAL([n]) -> [1] if n != 0 -/// OP_0NOTEQUAL([n]) -> [0] if n == 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_0notequal(stack: &mut Stack) -> bool { - let (op, desc) = (OP0NOTEQUAL, OP0NOTEQUAL_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n != ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - -/// OP_ADD: Adds the two numbers on top of the stack -/// -/// Example: OP_ADD([n1, n2]) -> [n1+n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_add(stack: &mut Stack) -> bool { - let (op, desc) = (OPADD, OPADD_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_add(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_SUB: Subtracts the number on top of the stack from the second-to-top number on the stack -/// -/// Example: OP_SUB([n1, n2]) -> [n1-n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_sub(stack: &mut Stack) -> bool { - let (op, desc) = (OPSUB, OPSUB_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_sub(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_MUL: Multiplies the second-to-top number by the number on top of the stack -/// -/// Example: OP_MUL([n1, n2]) -> [n1*n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_mul(stack: &mut Stack) -> bool { - let (op, desc) = (OPMUL, OPMUL_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_mul(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_DIV: Divides the second-to-top number by the number on top of the stack -/// -/// Example: OP_DIV([n1, n2]) -> [n1/n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_div(stack: &mut Stack) -> bool { - let (op, desc) = (OPDIV, OPDIV_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_div(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_MOD: Computes the remainder of the division of the second-to-top number by the number on top of the stack -/// -/// Example: OP_MOD([n1, n2]) -> [n1%n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_mod(stack: &mut Stack) -> bool { - let (op, desc) = (OPMOD, OPMOD_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_rem(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_LSHIFT: Computes the left shift of the second-to-top number by the number on top of the stack -/// -/// Example: OP_LSHIFT([n1, n2]) -> [n1< bool { - let (op, desc) = (OPLSHIFT, OPLSHIFT_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_shl(n2 as u32) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_RSHIFT: Computes the right shift of the second-to-top number by the number on top of the stack -/// -/// Example: OP_RSHIFT([n1, n2]) -> [n1>>n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_rshift(stack: &mut Stack) -> bool { - let (op, desc) = (OPRIGHT, OPRIGHT_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_shr(n2 as u32) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_BOOLAND: Substitutes the two numbers on top of the stack with ONE if they are both non-zero, with ZERO otherwise -/// -/// Example: OP_BOOLAND([n1, n2]) -> [1] if n1 != 0 and n2 != 0 -/// OP_BOOLAND([n1, n2]) -> [0] if n1 == 0 or n2 == 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_booland(stack: &mut Stack) -> bool { - let (op, desc) = (OPBOOLAND, OPBOOLAND_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n1 != ZERO && n2 != ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - -/// OP_BOOLOR: Substitutes the two numbers on top of the stack with ONE if they are not both ZERO, with ZERO otherwise -/// -/// Example: OP_BOOLOR([n1, n2]) -> [1] if n1 != 0 or n2 != 0 -/// OP_BOOLOR([n1, n2]) -> [0] if n1 == 0 and n2 == 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_boolor(stack: &mut Stack) -> bool { - let (op, desc) = (OPBOOLOR, OPBOOLOR_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n1 != ZERO || n2 != ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - /// OP_NUMEQUAL: Substitutes the two numbers on top of the stack with ONE if they are equal, with ZERO otherwise /// /// Example: OP_NUMEQUAL([n1, n2]) -> [1] if n1 == n2 @@ -2221,7 +1677,7 @@ pub fn op_checkmultisig(stack: &mut Stack) -> bool { /// /// * `stack` - mutable reference to the stack pub fn op_checkmultisigverify(stack: &mut Stack) -> bool { - let (op, desc) = (OPCHECKMULTISIG, OPCHECKMULTISIG_DESC); + let (op, desc) = (OPCHECKMULTISIGVERIFY, OPCHECKMULTISIGVERIFY_DESC); trace(op, desc); let n = match stack.pop() { Some(StackEntry::Num(n)) => n, @@ -2312,3 +1768,10 @@ fn verify_multisig(sigs: &[Signature], msg: &String, pks: &mut Vec) - } num_valid_sigs == sigs.len() } + +/// Updates metadata with the transaction hash as the new genesis hash for item asset type transactions +pub fn update_metadata_with_tx_hash(transaction: &mut Transaction) { + if transaction.asset_type == AssetType::Item { + transaction.genesis_hash = transaction.hash.clone(); + } +} \ No newline at end of file diff --git a/src/validation/transaction_validator.rs b/src/validation/transaction_validator.rs new file mode 100644 index 0000000..b3b363a --- /dev/null +++ b/src/validation/transaction_validator.rs @@ -0,0 +1,129 @@ +use crate::models::{Transaction, AssetType, Metadata}; +use crate::errors::ValidationError; +use crate::utils::hash::calculate_hash; + +pub struct TransactionValidator; + +impl TransactionValidator { + pub fn validate(transaction: &Transaction) -> Result<(), ValidationError> { + // Existing validation logic + Self::validate_basic(transaction)?; + + // Additional validation for item asset type transactions + if let AssetType::Item = transaction.asset_type { + Self::validate_item_genesis_hash(transaction)?; + } + + Ok(()) + } + + fn validate_basic(transaction: &Transaction) -> Result<(), ValidationError> { + // Implement existing basic validation logic + // For example, check signatures, format, etc. + if transaction.signature.is_empty() { + return Err(ValidationError::InvalidSignature); + } + // Add other basic validations as needed + Ok(()) + } + + fn validate_item_genesis_hash(transaction: &Transaction) -> Result<(), ValidationError> { + if let Some(metadata) = &transaction.metadata { + let genesis_hash = &metadata.genesis_hash; + let expected_genesis_hash = calculate_hash(&transaction.id); + if genesis_hash != &expected_genesis_hash { + return Err(ValidationError::InvalidGenesisHash { + expected: expected_genesis_hash, + found: genesis_hash.clone(), + }); + } + } else { + return Err(ValidationError::MissingMetadata); + } + Ok(()) + } + + pub fn update_metadata(transaction: &mut Transaction) -> Result<(), ValidationError> { + if let AssetType::Item = transaction.asset_type { + let new_genesis_hash = calculate_hash(&transaction.id); + if let Some(metadata) = &mut transaction.metadata { + metadata.genesis_hash = new_genesis_hash; + } else { + return Err(ValidationError::MissingMetadata); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::{Transaction, AssetType, Metadata}; + + #[test] + fn test_validate_item_genesis_hash_success() { + let transaction_id = "tx123"; + let transaction = Transaction { + id: transaction_id.to_string(), + asset_type: AssetType::Item, + metadata: Some(Metadata { + genesis_hash: calculate_hash(transaction_id), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + assert!(TransactionValidator::validate(&transaction).is_ok()); + } + + #[test] + fn test_validate_item_genesis_hash_failure() { + let transaction_id = "tx123"; + let transaction = Transaction { + id: transaction_id.to_string(), + asset_type: AssetType::Item, + metadata: Some(Metadata { + genesis_hash: "invalid_hash".to_string(), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + let result = TransactionValidator::validate(&transaction); + assert!(matches!(result, Err(ValidationError::InvalidGenesisHash { .. }))); + } + + #[test] + fn test_update_metadata() { + let mut transaction = Transaction { + id: "tx123".to_string(), + asset_type: AssetType::Item, + metadata: Some(Metadata { + genesis_hash: "old_hash".to_string(), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + let new_hash = calculate_hash(&transaction.id); + TransactionValidator::update_metadata(&mut transaction).unwrap(); + assert_eq!(transaction.metadata.as_ref().unwrap().genesis_hash, new_hash); + } + + #[test] + fn test_update_metadata_non_item_asset() { + let mut transaction = Transaction { + id: "tx123".to_string(), + asset_type: AssetType::Currency, + metadata: Some(Metadata { + genesis_hash: "old_hash".to_string(), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + TransactionValidator::update_metadata(&mut transaction).unwrap(); + assert_eq!(transaction.metadata.as_ref().unwrap().genesis_hash, "old_hash".to_string()); + } +} \ No newline at end of file