Skip to content

Commit

Permalink
feat(applying): check tx metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
MaicoLeberle committed Jan 11, 2024
1 parent 0c4251e commit d018ede
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 38 deletions.
20 changes: 16 additions & 4 deletions pallas-applying/src/alonzo.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Utilities required for Shelley-era transaction validation.
use crate::utils::{
add_minted_value, add_values, empty_value, get_alonzo_comp_tx_size,
add_minted_value, add_values, empty_value, extract_auxiliary_data, get_alonzo_comp_tx_size,
get_lovelace_from_alonzo_value, get_network_id_value, get_payment_part,
mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
AlonzoError::*,
Expand Down Expand Up @@ -743,7 +743,7 @@ fn check_vkey_input_wits(
None => return Err(Alonzo(InputNotInUTxO)),
}
}
check_remaining_vk_wits(vk_wits, tx_hash)
check_remaining_vk_wits(vk_wits, tx_hash) // required for native scripts
}

fn check_vk_wit(
Expand Down Expand Up @@ -824,8 +824,20 @@ fn check_languages(_mtx: &MintedTx, _prot_pps: &AlonzoProtParams) -> ValidationR
}

// The metadata of the transaction is valid.
fn check_metadata(_tx_body: &TransactionBody, _mtx: &MintedTx) -> ValidationResult {
Ok(())
fn check_metadata(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
match (&tx_body.auxiliary_data_hash, extract_auxiliary_data(mtx)) {
(Some(metadata_hash), Some(metadata)) => {
if metadata_hash.as_slice()
== pallas_crypto::hash::Hasher::<256>::hash(metadata).as_ref()
{
Ok(())
} else {
Err(Alonzo(MetadataHash))
}
}
(None, None) => Ok(()),
_ => Err(Alonzo(MetadataHash)),
}
}

// The script data integrity hash matches the hash of the redeemers, languages
Expand Down
24 changes: 7 additions & 17 deletions pallas-applying/src/shelley_ma.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
//! Utilities required for ShelleyMA-era transaction validation.
use crate::utils::{
add_minted_value, add_values, empty_value, get_alonzo_comp_tx_size, get_payment_part,
get_shelley_address, mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
FeePolicy,
add_minted_value, add_values, empty_value, extract_auxiliary_data, get_alonzo_comp_tx_size,
get_payment_part, get_shelley_address, mk_alonzo_vk_wits_check_list, values_are_equal,
verify_signature, FeePolicy,
ShelleyMAError::*,
ShelleyProtParams, UTxOs,
ValidationError::{self, *},
ValidationResult,
};
use pallas_addresses::{PaymentKeyHash, ScriptHash, ShelleyAddress, ShelleyPaymentPart};
use pallas_codec::{
minicbor::encode,
utils::{Bytes, KeepRaw},
};
use pallas_codec::{minicbor::encode, utils::KeepRaw};
use pallas_primitives::{
alonzo::{
AuxiliaryData, MintedTx, MintedWitnessSet, Multiasset, NativeScript, PolicyId,
TransactionBody, TransactionOutput, VKeyWitness, Value,
MintedTx, MintedWitnessSet, Multiasset, NativeScript, PolicyId, TransactionBody,
TransactionOutput, VKeyWitness, Value,
},
byron::TxOut,
};
Expand Down Expand Up @@ -48,12 +45,6 @@ pub fn validate_shelley_ma_tx(
check_minting(tx_body, mtx)
}

fn extract_auxiliary_data<'a>(mtx: &'a MintedTx) -> Option<&'a [u8]> {
Option::<KeepRaw<AuxiliaryData>>::from((mtx.auxiliary_data).clone())
.as_ref()
.map(KeepRaw::raw_cbor)
}

fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
if tx_body.inputs.is_empty() {
return Err(ShelleyMA(TxInsEmpty));
Expand Down Expand Up @@ -193,8 +184,7 @@ fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationRes
}

fn check_metadata(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
let auxiliary_data_hash: &Option<Bytes> = &tx_body.auxiliary_data_hash;
match (auxiliary_data_hash, &extract_auxiliary_data(mtx)) {
match (&tx_body.auxiliary_data_hash, extract_auxiliary_data(mtx)) {
(Some(metadata_hash), Some(metadata)) => {
if metadata_hash.as_slice()
== pallas_crypto::hash::Hasher::<256>::hash(metadata).as_ref()
Expand Down
15 changes: 12 additions & 3 deletions pallas-applying/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ pub mod validation;

pub use environment::*;
use pallas_addresses::{Address, ShelleyAddress, ShelleyPaymentPart};
use pallas_codec::{minicbor::encode, utils::KeyValuePairs};
use pallas_codec::{
minicbor::encode,
utils::{KeepRaw, KeyValuePairs},
};
use pallas_crypto::key::ed25519::{PublicKey, Signature};
use pallas_primitives::alonzo::{
AssetName, Coin, Multiasset, NetworkId, PolicyId, TransactionBody, TransactionOutput,
VKeyWitness, Value,
AssetName, AuxiliaryData, Coin, MintedTx, Multiasset, NetworkId, PolicyId, TransactionBody,
TransactionOutput, VKeyWitness, Value,
};
use pallas_traverse::{MultiEraInput, MultiEraOutput};
use std::collections::HashMap;
Expand Down Expand Up @@ -242,3 +245,9 @@ pub fn get_shelley_address(address: Vec<u8>) -> Option<ShelleyAddress> {
_ => None,
}
}

pub fn extract_auxiliary_data<'a>(mtx: &'a MintedTx) -> Option<&'a [u8]> {
Option::<KeepRaw<AuxiliaryData>>::from((mtx.auxiliary_data).clone())
.as_ref()
.map(KeepRaw::raw_cbor)
}
1 change: 1 addition & 0 deletions pallas-applying/src/utils/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub enum AlonzoError {
UnneededRedeemer,
DatumMissing,
UnneededDatum,
MetadataHash,
}

pub type ValidationResult = Result<(), ValidationError>;
90 changes: 89 additions & 1 deletion pallas-applying/tests/alonzo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use pallas_codec::{
decode::{Decode, Decoder},
encode,
},
utils::{Bytes, KeepRaw, KeyValuePairs},
utils::{Bytes, KeepRaw, KeyValuePairs, Nullable},
};
use pallas_primitives::alonzo::{
AddrKeyhash, ExUnits, MintedTx, MintedWitnessSet, NativeScript, NetworkId, PlutusData,
Expand Down Expand Up @@ -227,6 +227,48 @@ mod alonzo_tests {
}
}

#[test]
// Transaction hash:
// 8b6debb3340e5dac098ddb25fa647a99de12a6c1987c98b17ae074d6917dba16
fn successful_mainnet_tx_with_metadata() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/alonzo4.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/alonzo4.address")),
Value::Coin(3224834468),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Alonzo(AlonzoProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 16384,
languages: vec![Language::PlutusV1, Language::PlutusV2],
max_block_ex_mem: 50000000,
max_block_ex_steps: 40000000000,
max_tx_ex_mem: 10000000,
max_tx_ex_steps: 10000000000,
max_val_size: 5000,
collateral_percent: 150,
max_collateral_inputs: 3,
coints_per_utxo_word: 34482,
}),
prot_magic: 764824073,
block_slot: 6447038,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?})", err),
}
}

#[test]
// Same as succesful_mainnet_tx, except that all inputs are removed.
fn empty_ins() {
Expand Down Expand Up @@ -2503,4 +2545,50 @@ mod alonzo_tests {
},
}
}

#[test]
// Same as successful_mainnet_tx_with_metadata, except that the AuxiliaryData is
// removed.
fn auxiliary_data_removed() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/alonzo4.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
mtx.auxiliary_data = Nullable::Null;
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/alonzo4.address")),
Value::Coin(3224834468),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Alonzo(AlonzoProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 16384,
languages: vec![Language::PlutusV1, Language::PlutusV2],
max_block_ex_mem: 50000000,
max_block_ex_steps: 40000000000,
max_tx_ex_mem: 10000000,
max_tx_ex_steps: 10000000000,
max_val_size: 5000,
collateral_percent: 150,
max_collateral_inputs: 3,
coints_per_utxo_word: 34482,
}),
prot_magic: 764824073,
block_slot: 6447038,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Unneeded redeemer"),
Err(err) => match err {
Alonzo(MetadataHash) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
}
31 changes: 18 additions & 13 deletions pallas-applying/tests/shelley_ma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use pallas_codec::{
decode::{Decode, Decoder},
encode,
},
utils::Bytes,
utils::{Bytes, Nullable},
};
use pallas_primitives::alonzo::{
MintedTx, MintedWitnessSet, TransactionBody, TransactionOutput, VKeyWitness, Value,
Expand Down Expand Up @@ -175,7 +175,7 @@ mod shelley_ma_tests {
)],
);
// Clear the set of inputs in the transaction.
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
let mut tx_body: TransactionBody = mtx.transaction_body.unwrap().clone();
tx_body.inputs = Vec::new();
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_body, &mut tx_buf) {
Expand Down Expand Up @@ -249,7 +249,7 @@ mod shelley_ma_tests {
None,
)],
);
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
let mut tx_body: TransactionBody = mtx.transaction_body.unwrap().clone();
tx_body.ttl = None;
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_body, &mut tx_buf) {
Expand Down Expand Up @@ -396,7 +396,7 @@ mod shelley_ma_tests {
fn preservation_of_value() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
let mut tx_body: TransactionBody = mtx.transaction_body.unwrap().clone();
tx_body.fee = tx_body.fee - 1;
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_body, &mut tx_buf) {
Expand Down Expand Up @@ -479,7 +479,7 @@ mod shelley_ma_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
let mut tx_body: TransactionBody = mtx.transaction_body.unwrap().clone();
let (first_output, rest): (&TransactionOutput, &[TransactionOutput]) =
(&tx_body.outputs).split_first().unwrap();
let addr: ShelleyAddress =
Expand Down Expand Up @@ -540,11 +540,13 @@ mod shelley_ma_tests {
}

#[test]
// Like successful_mainnet_shelley_tx_with_metadata (hash:
// c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a)
// Same as successful_mainnet_shelley_tx_with_metadata (hash:
// c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a), except
// that the AuxiliaryData is removed.
fn auxiliary_data_removed() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley3.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
mtx.auxiliary_data = Nullable::Null;
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
Expand All @@ -568,8 +570,11 @@ mod shelley_ma_tests {
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?})", err),
Ok(()) => assert!(false, "Output with wrong network ID should be rejected"),
Err(err) => match err {
ShelleyMA(MetadataHash) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}

Expand All @@ -581,7 +586,7 @@ mod shelley_ma_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
let mut tx_wits: MintedWitnessSet = mtx.transaction_witness_set.unwrap().clone();
tx_wits.vkeywitness = Some(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_wits, &mut tx_buf) {
Expand Down Expand Up @@ -629,7 +634,7 @@ mod shelley_ma_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
let mut tx_wits: MintedWitnessSet = mtx.transaction_witness_set.unwrap().clone();
let mut wit: VKeyWitness = tx_wits.vkeywitness.clone().unwrap().pop().unwrap();
let mut sig_as_vec: Vec<u8> = wit.signature.to_vec();
sig_as_vec.pop();
Expand Down Expand Up @@ -682,7 +687,7 @@ mod shelley_ma_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley2.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
let mut tx_wits: MintedWitnessSet = mtx.transaction_witness_set.unwrap().clone();
tx_wits.native_script = Some(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_wits, &mut tx_buf) {
Expand Down
1 change: 1 addition & 0 deletions test_data/alonzo4.address
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
01f64b141bfa7761c00a48a137b15d433af02c9275dbf52ea95566b59cb4f05ecc9fd8c9066ef7fd907db854c76caf6462b132ce133dc7cc44
1 change: 1 addition & 0 deletions test_data/alonzo4.tx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
84a70081825820c514db191f8220a2603cee0c07adf7428a26ef9b44f08d2659a373da20bb144d010d80018282583901a548aefcaf1004185c48d0a9299e99c0f40afe59eb26da9d16d70c9252ff4e28d220bbd1b2c4661c9082bc169e4f59658078859bccf167bd1a0716549782583901a0526f3cf9c8cb3438532cdbd1f1739f4c8c50019d451fcd3308bfc6b4f05ecc9fd8c9066ef7fd907db854c76caf6462b132ce133dc7cc441ab91e1748021a0002a5c5031a02a31e570e80075820dc8240e5e3b815f383d2e43859a29481a875d7c6dcadef5e64a958a93a1b00c5a1008182582082b9f680f097c8bb43ced135459f4a5b11d6fb709b638d12c6f75fcfaf623e8558405476f3e15c89109a108a21cf85cb0144830656a234df6c655b493202f89e85257de1e76be5da6538bdf323b9c8b1559622d9ef9b6bb1b00c3ae6111e16a08502f5d90103a100a100784043354143363344373143463044393737363933373733383938443534314635454330423232323130434436424136393833434641363341334534463730333737

0 comments on commit d018ede

Please sign in to comment.