diff --git a/pallas-applying/docs/alonzo.md b/pallas-applying/docs/alonzo.md
index 273e213a4..9850ab05f 100644
--- a/pallas-applying/docs/alonzo.md
+++ b/pallas-applying/docs/alonzo.md
@@ -176,7 +176,7 @@ Let ***tx ∈ Tx*** be one of its Alonzo transactions, with transaction body ***
- **Each datum hash in a Plutus script input matches the hash of a datum in the transaction witness set**:
{h : (a,\_,h) ∈ txIns(txBody) ◁ utxo, isPlutusScriptAddress(txWits, a)} ⊆ {datumHash(d) : d ∈ txDats(txWits)}
- - **Each datum object in the transaction witness set corresponds either to an output datum hash or to the datum hash of a Plutus script input**:
+ - **Each datum object in the transaction witness set corresponds either to a Plutus script input datum hash or to an output datum hash**:
{datumHash(d): d ∈ txDats(txWits)} ⊆ {h: (a,\_,h) ∈ txIns(txBody) ◁ utxo, isPlutusScriptAddress(txWits, a)} ∪ {h: (\_,\_,h) ∈ txOuts(txBody)}
- **The set of redeemers in the transaction witness set should match the set of Plutus scripts needed to validate the transaction**:
diff --git a/pallas-applying/src/alonzo.rs b/pallas-applying/src/alonzo.rs
index eb04e388f..7a5517e5f 100644
--- a/pallas-applying/src/alonzo.rs
+++ b/pallas-applying/src/alonzo.rs
@@ -2,7 +2,8 @@
use crate::utils::{
add_minted_value, add_values, empty_value, get_alonzo_comp_tx_size,
- get_lovelace_from_alonzo_value, get_network_id_value, values_are_equal, verify_signature,
+ get_lovelace_from_alonzo_value, get_network_id_value, mk_alonzo_vk_wits_check_list,
+ values_are_equal, verify_signature,
AlonzoError::*,
AlonzoProtParams, FeePolicy, UTxOs,
ValidationError::{self, *},
@@ -17,7 +18,7 @@ use pallas_primitives::{
},
byron::TxOut,
};
-use pallas_traverse::{ComputeHash, MultiEraInput, MultiEraOutput};
+use pallas_traverse::{MultiEraInput, MultiEraOutput, OriginalHash};
pub fn validate_alonzo_tx(
mtx: &MintedTx,
@@ -38,7 +39,7 @@ pub fn validate_alonzo_tx(
check_network_id(tx_body, network_id)?;
check_tx_size(size, prot_pps)?;
check_tx_ex_units(mtx, prot_pps)?;
- check_witnesses(tx_body, utxos, mtx)?;
+ check_witness_set(mtx, utxos)?;
check_languages(mtx, prot_pps)?;
check_metadata(tx_body, mtx)?;
check_script_data_hash(tx_body, mtx, prot_pps)?;
@@ -392,10 +393,11 @@ fn check_tx_ex_units(mtx: &MintedTx, prot_pps: &AlonzoProtParams) -> ValidationR
Ok(())
}
-fn check_witnesses(tx_body: &TransactionBody, utxos: &UTxOs, mtx: &MintedTx) -> ValidationResult {
+fn check_witness_set(mtx: &MintedTx, utxos: &UTxOs) -> ValidationResult {
+ let tx_hash: &Vec = &Vec::from(mtx.transaction_body.original_hash().as_ref());
+ let tx_body: &TransactionBody = &mtx.transaction_body;
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
let vkey_wits: &Option> = &tx_wits.vkeywitness;
- let tx_hash: &Vec = &Vec::from(tx_body.compute_hash().as_ref());
check_needed_scripts_are_included(
tx_body,
utxos,
@@ -404,8 +406,8 @@ fn check_witnesses(tx_body: &TransactionBody, utxos: &UTxOs, mtx: &MintedTx) ->
)?;
check_datums(tx_body, utxos, &tx_wits.plutus_data)?;
check_redeemers(tx_body, utxos, tx_wits)?;
- check_vkey_input_wits(tx_body, utxos, &tx_wits.vkeywitness)?;
- check_required_signers(&tx_body.required_signers, vkey_wits, tx_hash)
+ check_required_signers(&tx_body.required_signers, vkey_wits, tx_hash)?;
+ check_vkey_input_wits(mtx, &tx_wits.vkeywitness, utxos)
}
// The set of needed scripts (minting policies, native scripts and Plutus
@@ -460,9 +462,59 @@ fn check_redeemers(
// The owner of each transaction input and each collateral input should have
// signed the transaction.
fn check_vkey_input_wits(
- _tx_body: &TransactionBody,
- _utxos: &UTxOs,
- _vkey_wits: &Option>,
+ mtx: &MintedTx,
+ vkey_wits: &Option>,
+ utxos: &UTxOs,
+) -> ValidationResult {
+ let tx_body: &TransactionBody = &mtx.transaction_body;
+ let vk_wits: &mut Vec<(bool, VKeyWitness)> =
+ &mut mk_alonzo_vk_wits_check_list(vkey_wits, Alonzo(MissingVKWitness))?;
+ let tx_hash: &Vec = &Vec::from(mtx.transaction_body.original_hash().as_ref());
+ let mut inputs_and_collaterals: Vec = Vec::new();
+ inputs_and_collaterals.extend(tx_body.inputs.clone());
+ match &tx_body.collateral {
+ Some(collaterals) => inputs_and_collaterals.extend(collaterals.clone()),
+ None => (),
+ }
+ for input in inputs_and_collaterals.iter() {
+ match utxos.get(&MultiEraInput::from_alonzo_compatible(input)) {
+ Some(multi_era_output) => {
+ if let Some(alonzo_comp_output) = MultiEraOutput::as_alonzo(multi_era_output) {
+ match get_payment_part(alonzo_comp_output)? {
+ ShelleyPaymentPart::Key(payment_key_hash) => {
+ check_vk_wit(&payment_key_hash, vk_wits, tx_hash)?
+ }
+ ShelleyPaymentPart::Script(_) => (),
+ }
+ }
+ }
+ None => return Err(Alonzo(InputNotInUTxO)),
+ }
+ }
+ check_remaining_vk_wits(vk_wits, tx_hash)
+}
+
+fn check_vk_wit(
+ payment_key_hash: &AddrKeyhash,
+ wits: &mut Vec<(bool, VKeyWitness)>,
+ data_to_verify: &Vec,
+) -> ValidationResult {
+ for (found, vkey_wit) in wits {
+ if pallas_crypto::hash::Hasher::<224>::hash(&vkey_wit.vkey.clone()) == *payment_key_hash {
+ if !verify_signature(vkey_wit, data_to_verify) {
+ return Err(Alonzo(VKWrongSignature));
+ } else {
+ *found = true;
+ return Ok(());
+ }
+ }
+ }
+ Err(Alonzo(MissingVKWitness))
+}
+
+fn check_remaining_vk_wits(
+ _vk_wits: &mut [(bool, VKeyWitness)],
+ _data_to_verify: &[u8],
) -> ValidationResult {
Ok(())
}
@@ -472,13 +524,13 @@ fn check_vkey_input_wits(
fn check_required_signers(
required_signers: &Option,
vkey_wits: &Option>,
- tx_hash: &Vec,
+ data_to_verify: &Vec,
) -> ValidationResult {
if let Some(req_signers) = &required_signers {
match &vkey_wits {
Some(vkey_wits) => {
for req_signer in req_signers {
- check_required_signer(req_signer, vkey_wits, tx_hash)?
+ find_and_check_req_signer(req_signer, vkey_wits, data_to_verify)?
}
}
None => return Err(Alonzo(MissingReqSigner)),
@@ -488,16 +540,14 @@ fn check_required_signers(
}
// Try to find the verification key in the witnesses, and verify the signature.
-fn check_required_signer(
- req_signer: &AddrKeyhash,
+fn find_and_check_req_signer(
+ vkey_hash: &AddrKeyhash,
vkey_wits: &Vec,
- tx_hash: &Vec,
+ data_to_verify: &Vec,
) -> ValidationResult {
for vkey_wit in vkey_wits {
- if pallas_crypto::hash::Hasher::<224>::hash(&Vec::::from(vkey_wit.vkey.clone()))
- == *req_signer
- {
- if !verify_signature(vkey_wit, tx_hash) {
+ if pallas_crypto::hash::Hasher::<224>::hash(&vkey_wit.vkey.clone()) == *vkey_hash {
+ if !verify_signature(vkey_wit, data_to_verify) {
return Err(Alonzo(ReqSignerWrongSig));
} else {
return Ok(());
diff --git a/pallas-applying/src/shelley_ma.rs b/pallas-applying/src/shelley_ma.rs
index 705fcc59c..d5e0e6a1d 100644
--- a/pallas-applying/src/shelley_ma.rs
+++ b/pallas-applying/src/shelley_ma.rs
@@ -32,6 +32,7 @@ pub fn validate_shelley_ma_tx(
era: &Era,
) -> ValidationResult {
let tx_body: &TransactionBody = &mtx.transaction_body;
+ let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
let size: &u64 = &get_alonzo_comp_tx_size(tx_body).ok_or(ShelleyMA(UnknownTxSize))?;
check_ins_not_empty(tx_body)?;
check_ins_in_utxos(tx_body, utxos)?;
@@ -42,7 +43,7 @@ pub fn validate_shelley_ma_tx(
check_fees(tx_body, size, prot_pps)?;
check_network_id(tx_body, network_id)?;
check_metadata(tx_body, mtx)?;
- check_witnesses(tx_body, utxos, mtx)?;
+ check_witnesses(tx_body, tx_wits, utxos)?;
check_minting(tx_body, mtx)
}
@@ -214,8 +215,11 @@ fn check_metadata(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult
}
}
-fn check_witnesses(tx_body: &TransactionBody, utxos: &UTxOs, mtx: &MintedTx) -> ValidationResult {
- let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
+fn check_witnesses(
+ tx_body: &TransactionBody,
+ tx_wits: &MintedWitnessSet,
+ utxos: &UTxOs,
+) -> ValidationResult {
let vk_wits: &mut Vec<(bool, VKeyWitness)> =
&mut mk_alonzo_vk_wits_check_list(&tx_wits.vkeywitness, ShelleyMA(MissingVKWitness))?;
let tx_hash: &Vec = &Vec::from(tx_body.compute_hash().as_ref());
diff --git a/pallas-applying/src/utils/validation.rs b/pallas-applying/src/utils/validation.rs
index 03ce8433f..1678fb0b8 100644
--- a/pallas-applying/src/utils/validation.rs
+++ b/pallas-applying/src/utils/validation.rs
@@ -74,6 +74,8 @@ pub enum AlonzoError {
RedeemerMissing,
TxExUnitsExceeded,
MaxTxSizeExceeded,
+ MissingVKWitness,
+ VKWrongSignature,
MissingReqSigner,
ReqSignerWrongSig,
}
diff --git a/pallas-applying/tests/alonzo.rs b/pallas-applying/tests/alonzo.rs
index f385a3050..0566b6818 100644
--- a/pallas-applying/tests/alonzo.rs
+++ b/pallas-applying/tests/alonzo.rs
@@ -1,6 +1,7 @@
pub mod common;
use common::*;
+use hex;
use pallas_addresses::{Address, Network, ShelleyAddress, ShelleyPaymentPart};
use pallas_applying::{
utils::{
@@ -16,9 +17,9 @@ use pallas_codec::{
},
utils::{Bytes, KeyValuePairs},
};
-use pallas_crypto::hash::Hash;
use pallas_primitives::alonzo::{
- AddrKeyhash, MintedTx, NetworkId, TransactionBody, TransactionOutput, Value,
+ AddrKeyhash, MintedTx, MintedWitnessSet, NetworkId, TransactionBody, TransactionOutput,
+ VKeyWitness, Value,
};
use pallas_traverse::{Era, MultiEraInput, MultiEraOutput, MultiEraTx};
use std::borrow::Cow;
@@ -86,10 +87,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -444,10 +444,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -567,10 +566,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -678,10 +676,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -813,10 +810,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -877,10 +873,9 @@ mod alonzo_tests {
Value::Multiasset(
5000000,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80, 249,
- 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1000,
@@ -937,10 +932,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -1239,10 +1233,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -1400,10 +1393,9 @@ mod alonzo_tests {
Value::Multiasset(
1724100,
KeyValuePairs::from(Vec::from([(
- Hash::<28>::new([
- 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80,
- 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149,
- ]),
+ "b001076b34a87e7d48ec46703a6f50f93289582ad9bdbeff7f1e3295"
+ .parse()
+ .unwrap(),
KeyValuePairs::from(Vec::from([(
Bytes::from(hex::decode("4879706562656173747332343233").unwrap()),
1,
@@ -1512,4 +1504,119 @@ mod alonzo_tests {
},
}
}
+
+ #[test]
+ // Same as successful_mainnet_tx, except that the list of verification key is
+ // empty.
+ fn missing_vk_witness() {
+ let cbor_bytes: Vec = cbor_to_bytes(include_str!("../../test_data/alonzo1.tx"));
+ let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
+ let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
+ &mtx.transaction_body,
+ &[(
+ String::from(include_str!("../../test_data/alonzo1.address")),
+ Value::Coin(1549646822),
+ None,
+ )],
+ );
+ let mut tx_wits: MintedWitnessSet = mtx.transaction_witness_set.unwrap().clone();
+ tx_wits.vkeywitness = Some(vec![]);
+ let mut tx_buf: Vec = Vec::new();
+ match encode(tx_wits, &mut tx_buf) {
+ Ok(_) => (),
+ Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
+ };
+ mtx.transaction_witness_set =
+ Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
+ let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
+ 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: 44237276,
+ network_id: 1,
+ };
+ match validate(&metx, &utxos, &env) {
+ Ok(()) => assert!(false, "Missing verification key witness"),
+ Err(err) => match err {
+ Alonzo(MissingVKWitness) => (),
+ _ => assert!(false, "Unexpected error ({:?})", err),
+ },
+ }
+ }
+
+ #[test]
+ // Same as successful_mainnet_tx, except that the signature of the only witness
+ // of the transaction is modified.
+ fn wrong_signature() {
+ let cbor_bytes: Vec = cbor_to_bytes(include_str!("../../test_data/alonzo1.tx"));
+ let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
+ let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
+ &mtx.transaction_body,
+ &[(
+ String::from(include_str!("../../test_data/alonzo1.address")),
+ Value::Coin(1549646822),
+ None,
+ )],
+ );
+ let mut tx_wits: MintedWitnessSet = mtx.transaction_witness_set.unwrap().clone();
+ let mut wit: VKeyWitness = tx_wits.vkeywitness.clone().unwrap().pop().unwrap();
+ // "c50047bafa1adfbfd588d7c8be89f7ab17aecd47c4cc0ed5c1318caca57c8215d77d6878f0eb2bd2620b4ea552415a3028f98102275c9a564278d0f4e6425d02"
+ // is replaced with
+ // "c50047bafa1adfbfd588d7c8be89f7ab17aecd47c4cc0ed5c1318caca57c8215d77d6878f0eb2bd2620b4ea552415a3028f98102275c9a564278d0f400000000"
+ wit.signature = hex::decode(
+ "c50047bafa1adfbfd588d7c8be89f7ab17aecd47c4cc0ed5c1318caca57c8215d77d6878f0eb2bd2620b4ea552415a3028f98102275c9a564278d0f400000000"
+ ).unwrap().into();
+ tx_wits.vkeywitness = Some(vec![wit]);
+ let mut tx_buf: Vec = Vec::new();
+ match encode(tx_wits, &mut tx_buf) {
+ Ok(_) => (),
+ Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
+ };
+ mtx.transaction_witness_set =
+ Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
+ let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
+ 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: 44237276,
+ network_id: 1,
+ };
+ match validate(&metx, &utxos, &env) {
+ Ok(()) => assert!(false, "Witness signature should verify the transaction"),
+ Err(err) => match err {
+ Alonzo(VKWrongSignature) => (),
+ _ => assert!(false, "Unexpected error ({:?})", err),
+ },
+ }
+ }
}