feat(applying): implement Alonzo phase-1 validations (#380)

This commit is contained in:
Maico Leberle 2024-01-19 22:19:46 -03:00 committed by GitHub
parent 7cb1ffe100
commit 7b2894e4a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 4900 additions and 704 deletions

View file

@ -19,6 +19,7 @@ pallas-crypto = { version = "=0.21.0", path = "../pallas-crypto" }
pallas-primitives = { version = "=0.21.0", path = "../pallas-primitives" }
pallas-traverse = { version = "=0.21.0", path = "../pallas-traverse" }
rand = "0.8"
hex = "0.4"
[dev-dependencies]
hex = "0.4"

View file

@ -0,0 +1,205 @@
# Alonzo phase-1 validation rules
This document covers the Alonzo era. This document covers the concepts, notation and validation rules realted to phase-1 validation in the Alonzo ledger. For further information, refer to the [Alonzo ledger white paper](https://github.com/input-output-hk/cardano-ledger/releases/latest/download/alonzo-ledger.pdf).
## Definitions and notation
- **Blocks**:
- ***Block*** is the set of all possible (not necessarily valid) Alonzo blocks. When clear, we will write ***block ∈ Blocks*** to refer to the current block being validated.
- ***txs(block)*** is the set of transactions of the block.
- ***blockExUnits(block) ∈ ExUnits***, where ***ExUnits := (, )***, is the memory and execution step units resulting from the sum of memory and execution step units of all its transactions. That is, ***blockExUnits(block) := (∑ tx ∈ txs(block): txExUnits(txWits(tx)))***, where addition of execution units is defined pointwise.
- **Transactions**:
- ***Tx*** is the set of all possible (not necessarily valid) Alonzo transactions, composed of a transaction body and a witness set. When clear, we will write ***tx*** to refer to the current transaction.
- ***txIsPhase1Valid(block, pps, tx) ∈ Bool*** indicates whether ***tx ∈ txs(block)*** is phase-1 valid under ***pps***.
- ***TxBody*** is the type of Alonzo transaction bodies. Each transaction body is composed of a set of inputs, a list of outputs, and related data.
- ***txBody(tx)*** is the transaction body of the transaction. When clear, we will write ***txBody*** to refer to the transaction body of the current transaction.
- ***TxOut = Addr x Value x DatumHash*** is the set of transaction outputs, where
- ***Addr*** is the set of transaction output addresses.
- ***Value*** is the type of multi-asset Alonzo values. We define addition, equality comparisons and ordering comparisons for them in a point-wise manner.
- ***getValue(txOut) ∈ Value*** gives the value contained in the transaction output.
- ***isADAOnly : Value -> Bool*** indicates whether a value contains only ADA assets.
- ***balance : P(TxOut) → Value*** gives the sum of all assets in a set of transaction outputs.
- ***adaValueOf : -> Value*** gives the ADA-only value representation of a natural number.
- ***valSize : Value -> *** gives the size of a value in bytes, when serialized.
- ***DatumHash ⊆ Bytes*** is the type of hashes computed from datums. This field is optional.
- ***txOuts(txBody) ∈ P(TxOut)*** gives the list of transaction outputs of the transaction body.
- ***balance : P(TxOut) → Value*** gives the sum of all multi-asset values in a set of transaction outputs.
- ***utxoEntrySize(txOut) ∈ *** gives the size of the transaction output when serialized, in bytes.
- ***TxIn = TxId x Ix*** is the set of transaction inputs, where
- ***TxId*** is the type of transaction IDs.
- ***Ix = *** is the set of indices (used to refer to a specific transaction output).
- ***txIns(txBody) ∈ P(TxIn)*** gives the set of *non-collateral* inputs of the transaction.
- ***collateral(txBody) ∈ P(TxIn)*** gives the set of *collateral* inputs of the transaction.
- ***txInsVKey(txBody) ∈ P(TxIn)*** gives the set of transaction inputs of the transaction which are verification-key locked.
- ***utxo : TxIn → TxOut*** is a (partial) map that gives the unspent transaction output (UTxO) associated with a transaction input.
- Given ***A ⊆ dom(utxo)***, we will write ***A ◁ utxo := {txOut ∈ TxOut / ∃ txIn ∈ dom utxo: utxo(txIn) = txOut}***. For example, we will write ***txIns(tx) ◁ utxo := {txOut ∈ TxOut / ∃ ti ∈ dom(utxo): utxo(txIn) = txOut}*** to express the set of unspent transaction outputs associated with the set of inputs of the transaction.
- ***txValidityInterval(txBody) ∈ (Slot, Slot)*** is the transaction validity interval, made of a lower and upper bound, both of which are optional.
- ***requiredSigners(txBody) ∈ P(KeyHash)*** is the set of hashes of verification keys required for the execution of Plutus scripts, where ***KeyHash ⊆ Bytes***.
- ***txSize(txBody) ∈ *** is the size of the transaction in bytes, when serialized.
- ***fee(txBody) ∈ *** is the fee paid by the transaction.
- ***minted(txBody)*** is the multi-asset value minted (or burned) in the transaction.
- ***PolicyID*** is the set of all possible policy IDs associated to multi-asset values. In particular, ***adaID ∈ Policy*** is the policy of lovelaces.
- ***consumed(utxo, txBody) ∈ *** is the *consumed value* of the transaction, which equals the sum of all multi-asset values in the inputs of the transaction.
- ***produced(txBody) ∈ *** is the *produced value* of the transaction, which equals the sum of all multi-asset values in the outputs of the transaction, plus the transaction fee, plus the minted value.
- ***txNetId(txBody) ∈ NetworkID*** gives the network ID of a transaction (not to be confused with the network ID of addresses of unspent transaction outputs).
- ***txWits(tx)*** is the transaction witness set. When clear, we will write ***txWits*** to refer to the transaction witness set of the current transaction.
- ***txExUnits(txWits) ∈ ExUnits*** is the total execution units of the transaction.
- ***txMD(tx)*** is the metadata of the transaction.
- ***hashMD(md)*** is the result of hasing metadata ***md***.
- ***txMDHash(txBody)*** is the metadata hash contained within the transaction body.
- **Addresses**:
- ***Addr*** is the set of all valid Alonzo addresses.
- ***hashAddr : Addr -> Bytes*** is the hashing function for addresses.
- ***NetworkId*** is the global network ID.
- ***netId : Addr -> NetworkID*** gives the network ID of an address.
- ***isVKeyAddress(addr) -> Bool*** assesses whether the address is that of a verification key.
- ***isPlutusScriptAddress(txWits, addr)*** assesses whether the address is that of a Plutus script.
- ***Time***:
- ***Slot ∈ *** is the set of slots. When necessary, we write ***slot ∈ Slot*** to refer to the slot associated to the current block.
- ***UTCTime*** is the system time (UTC time zone).
- ***EpochInfo*** is the Alonzo epoch info.
- ***SystemStart*** is the start time of the system.
- ***epochInfoSlotToUTCTime: EpochInfo -> SystemStart -> Slot -> UTCTime*** translates a slot number to system time. The result is not always computable, as the slot number may be too far in the future for the system to predict the exact time to which it refers.
- **Serialization**:
- ***Bytes*** is the set of byte arrays (a.k.a. data, upon which signatures are built).
- ***⟦_⟧<sub>A</sub> : A -> Bytes*** takes an element of type ***A*** and returns a byte array resulting from serializing it.
- **Hashing**:
- ***hash: A -> Bytes*** is the abstract function (considering that ***A*** is a generic type) we use to refer to a hashing function.
- ***keyHash: VKey -> KeyHash*** is the hashing function for verification keys, where ***KeyHash ⊆ Bytes***
- **Scripts**:
- ***Script*** is the set of all Alonzo scripts: minting policies, native scripts and Plutus scripts. We will use the term *script* to refer to any of these kinds of scripts.
- ***isPlutusScript(script) ∈ Bool*** assesses whether a script is a Plutus one (that is, it is not a native script).
- ***scriptDataHash(txBody) ∈ Bytes*** is the hash of script-related data (transaction redeemers and relevant protocol parameters).
- ***hashScriptIntegrity : PParams -> P((Tag, Ix, Redeemer, ExUnits)) -> Languages -> P(DaAtum) -> Bytes*** hashes the protocol parameters and data relevant to script execution.
- **Protocol Parameters**:
- We will write ***pps ∈ PParams*** to represent the set of Alonzo protocol parameters, each of which contains at least the following associated functions:
- ***maxBlockExUnits(pps) ∈ ExUnits*** gives the maximum memory and execution step units for a block.
- ***maxTxExUnits(pps) ∈ ExUnits*** gives the maximum memory and execution step units for a transaction.
- ***minFees(pps, txBody) ∈ *** gives the minimum number of lovelace that must be paid bys the transaction as fee.
- ***maxCollateralInputs(pps) ∈ *** gives the maximum number of collateral inputs allowed per transaction.
- ***maxTxSize(pps) ∈ *** gives the maximum size any transaction can have.
- ***maxValSize(pps) ∈ *** gives the maximum size in bytes allowed for values, when serialized.
- ***collateralPercent(pps) ∈ {0,...,100}*** gives the fee percentage (multiplied by 100) that all lovelace in collateral inputs should add up to.
- ***coinsPerUTxOWord(pps) ∈ *** is the number of lovelace a UTxO should contain per byte (when serialized). This is used to assess the minimum number of lovelace that an unspent transaction output should lock.
- ***costModels : PParams -> (Languages -> CostModel)*** takes the protocol parameters and returns a map associating languages to their cost models.
- ***Languages := {PlutusV1, PlutusV2}*** is the set of Alonzo languages.
- ***CostModel*** is the set of cost models.
- ***Witnesses***:
- ***TxWits*** is the set of all possible transaction witness set.
- ***VKey*** is the set of verification keys (a.k.a. public keys).
- ***SKey*** is the set of signing keys (a.k.a. private keys).
- ***Sig*** is the set of signatures (i.e., the result of signing a byte array using a signing key).
- ***sig : SKey x Bytes -> Sig*** is the signing function.
- ***verify : VKey x Sig x Bytes -> Bool*** assesses whether the result of applying the verification key to the signature equals the byte array parameter.
- The assumption is that if ***sk*** and ***vk*** are, respectively, a pair of secret and verification keys associated with one another. Thus, if ***sig(sk, d) = σ***, then it must be that ***verify(vk, σ, d) = true***.
- ***txVKWits(txWits) ⊆ P(VKey x Sig)*** gives the list of pairs of verification keys and signatures of the transaction.
- ***paymentCredential<sub>utxo</sub>(txIn) ∈ KeyHash*** gets from ***txIn*** the associated transaction output in ***utxo***, extracts the address contained in it, and returns its hash. In other words, given ***utxo*** and transaction input ***txIn*** such that ***utxo(txIn) = (a, \_, \_)***, we have that ***paymentCredential<sub>utxo</sub>(txIn) = hashAddr(a)***.
- ***txRedeemers(txWits) ⊆ P((Tag, Ix, Redeemer, ExUnits))*** is the set of redeemers of the transaction. This (seemingly artificial) conjunction of values of different types will be useful to assess phase-1 validity of the transaction in a concise way.
- To all phase-1 validation purposes, we restrict ***Tag*** to ***Tag = {Mint, Spend}***. This is used to indicate whether a script is used on minting purposes (native scripts and minting policies), or should be executed (native scripts and Plutus scripts).
- Recall that ***Ix := ***, and represents an index on a list-like structure.
- ***Redeemer*** is the low-level representation of a redeemer, required by executors to execute validation on Plutus scripts.
- ***scriptsNeeded(utxo, txBody) ∈ P((ScriptPurpose x ScriptHash))*** assembles all the ***ScriptPurpose*** terms for validation of every transaction that may require script validation, each one paired with the hash of the corresponding witnessing script. This collects hashes of both native and Plutus scripts.
- ***ScriptPurpose := {PolicyID, TxIn}*** indicates whether the script is related to minting purposes (***PolicyID***) or should be executed to spend an input of the transaction (***TxIn***).
- ***ScriptHash ⊆ Bytes*** is the type of validator hashes.
- ***scriptHash : Script -> ScriptHash*** is the hashing function for scripts.
- ***redeemerPointer: TxBody -> ScriptPurpose -> (Tag, Ix)*** builds a redeemer pointer (that is, a representation suitable for matching with ***txRedeemers(txWits)***), setting the tag according to the type of the script purpose, and the index according to the order of the item represented by the script purpose (either a policy ID or a transaction input) in its container. For example, applying ***redeemerPoint*** on script purpose ***txIn ∈ TxIn*** yields the index of ***txIn*** within ***txIns(txBody)***.
- ***txScripts(txWits) ⊆ P(Script)*** is the set of scripts in the transaction witness set, both native and Plutus.
- ***txDats(txWits) ∈ P(Datum)*** is the set of all script-related datum objects of the transaction.
- ***datumHash: Datum -> DatumHash*** is the application of the hashing function on a ***Datum*** value.
- ***languages(txWits) ∈ Languages*** is the set of *languages* required by the scripts of the transaction.
## Validation rules for blocks
Let ***block ∈ Block*** be an Alonzo block, and let ***tx ∈ Tx*** be one of its Alonzo transactions, with transaction body ***txBody ∈ TxBody*** and witness set ***txWits ∈ TxWits***. We say that ***block*** is a phase-1 valid block if and only if the total sum of execution units of all its transactions does not exceed the maximum allowed by the protocol, and all its transactions are phase-1 valid. That is, ***block*** is phase-1 valid if and only if:
<code>maxBlockExUnits(pps) ≥ blockExUnits(block) ∧ ∀ tx ∈ txs(block): txIsPhase1Valid(block, tx)</code>
## Validation rules for transactions
Let ***tx ∈ Tx*** be one of its Alonzo transactions, with transaction body ***txBody ∈ TxBody*** and witness set ***txWits***. We say that ***tx*** is a phase-1 valid transaction if and only if
- **The set of transaction inputs is not empty**:
<code>txIns(txBody) ≠ ∅</code>
- **All transaction inputs and collateral inputs are in the set of (yet) unspent transaction outputs**:
<code>txIns(txBody) collateral(txBody) ⊆ dom(utxo)</code>
- **The block slot is contained in the transaction validity interval**:
<code>slot ∈ txValidityInterval(txBody)</code>
- **The upper bound of the validity time interval is suitable for script execution**: if there are minting policies, native or Plutus scripts in the transaction, and the upper bound of its validity interval is defined, then the upper bound slot of the interval is translatable to system time. That is, if there are neeeded scripts in the transaction, then it is the case that ***txValidityInterval(txBody) := (\_, ub)*** where ***ub*** is defined.
- **Fees**:
- **The fee paid by the transaction should be greater than or equal to the minimum fee**:
<code>fee(txBody) ≥ minFees(pps, txBody)</code>
- **Collateral**: if there are Plutus scripts in the transaction, then
- **The set of collateral inputs is not empty**:
<code>collateral(txBody) ≠ ∅</code>
- **The number of collateral inputs is not above maximum**:
<code>∥collateral(txBody)∥ ≤ maxCollateralInputs(pps)</code>
- **Each collateral input refers to a verification-key address**:
<code>∀(a,\_,\_) ∈ collateral(txBody) ◁ utxo: isVKeyAddress(a)</code>
- **Collateral inputs contain only ADA**:
<code>isADAOnly(balance(collateral(txBody) ◁ utxo))</code>
- **The total lovelace contained in collateral inputs should be greater than or equal to the minimum fee percentage**:
<code>balance(collateral(txBody) ◁ utxo)) >= fee(txBody) * collateralPercent(pps)</code>
- **The preservation of value property holds**: Assuming no staking or delegation actions are involved, it should be that
<code>consumed(utxo, txBody) = produced(txBody) + fee(txBody) + minted(txBody)</code>
- **All transaction outputs should contain at least the minimum lovelace**:
<code>∀ txOut ∈ txOuts(txBody): adaValueOf(coinsPerUTxOWord(pps) * utxoEntrySize(txOut)) ≤ getValue(txOut)</code>
- **The size of the value in each of the outputs should not be greater than the maximum allowed**:
<code>valSize(getValue(txOut)) ≤ maxValSize(pps)</code>
- **The network ID of each output matches the global network ID**:
<code>∀(a,\_) ∈ txOuts(txBody): netId(a) = NetworkId</code>
- **The network ID of the transaction body is either undefined or equal to the global network ID**
- **The transaction size does not exceed the protocol limit**:
<code>txSize(txBody) ≤ maxTxSize(pps)</code>
- **The number of execution units of the transaction should not exceed the maximum allowed**:
<code>txExUnits(txBody) ≤ maxTxExUnits(pps)</code>
- **Witnesses**:
- **Minting policy, native script and Plutus script witnesses**:
- **The set of needed scripts (minting policies, native scripts and Plutus scripts needed to validate the transaction) equals the set of scripts contained in the transaction witnesses set**:
<code>{h: (\_, h) ∈ scriptsNeeded(utxo, txBody)} = {scriptHash(s) : s ∈ txScripts(txWits)}</code>
- **Each datum hash in a Plutus script input matches the hash of a datum in the transaction witness set**:
<code>{h : (a,\_,h) ∈ txIns(txBody) ◁ utxo, isPlutusScriptAddress(txWits, a)} ⊆ {datumHash(d) : d ∈ txDats(txWits)}</code>
- **Each datum object in the transaction witness set corresponds either to a Plutus script input datum hash or to an output datum hash**:
<code>{datumHash(d): d ∈ txDats(txWits)} ⊆ {h: (a,\_,h) ∈ txIns(txBody) ◁ utxo, isPlutusScriptAddress(txWits, a)} {h: (\_,\_,h) ∈ txOuts(txBody)}</code>
- **The set of redeemers in the transaction witness set should match the set of Plutus scripts needed to validate the transaction**:
<code>{(tag, index): (tag, index, \_, \_) ∈ txRedeemers(txWits)} = {redeemerPointer(txBody, sp): (sp, h) ∈ scriptsNeeded(utxo, txBody), (∃s ∈ txScripts(txWits): isPlutusScript(s), h = scriptHash(s)}</code>
- **Verification-key witnesses**:
- **The owner of each transaction input and each collateral input should have signed the transaction**: for each ***txIn ∈ txInsVKey(txBody)*** there should exist ***(vk, σ) ∈ txVKWits(tx)*** such that:
- <code>verify(vk, σ, ⟦txBody⟧<sub>TxBody</sub>)</code>
- <code>paymentCredential<sub>utxo</sub>(txIn) = keyHash(vk)</code>
- **All required signers (needed by a Plutus script) have a corresponding match in the transaction witness set**: for each ***key_hash ∈ requiredSigners(txBody)***, there should exist ***(vk, σ) ∈ txVKWits(tx)*** such that:
- <code>verify(vk, σ, ⟦txBody⟧<sub>TxBody</sub>)</code>
- <code>keyHash(vk) = key_hash</code>
- **The required script languages are included in the protocol parameters**:
<code>languages(txWits) ⊆ {l : (l -> _) ∈ costModels(pps, language)}</code>
- **The metadata of the transaction is valid**:
<code>txMDHash(tx) = hashMD(txMD(tx))</code>
- **The script data integrity hash matches the hash of the redeemers, languages and datums of the transaction witness set**:
<code>scriptDataHash(txBody) = hashScriptIntegrity(pps, txRedeemers(txWits), languages(txWits), txDats(txWits))</code>
- **No ADA is minted**:
<code>adaID ∉ policies(mint(txBody))</code>

View file

@ -1,4 +1,4 @@
# Byron transaction validation rules
# Byron phase-1 validation rules
Refer to the [Byron's ledger white paper](https://github.com/input-output-hk/cardano-ledger/releases/latest/download/byron-ledger.pdf) for further information.

View file

@ -1,4 +1,4 @@
# ShelleyMA transaction validation rules
# ShelleyMA phase-1 validation rules
This document covers the Shelley era, including its Allegra and Mary hard forks. We write *ShelleyMA* to refer to any of these ledger versions, and *Shelley*, *Allegra* or *Mary* when the discrimination is relevant. This document covers only the concepts, notation and validation rules realted to phase-1 validation in these ledger versions. For further information, refer to the corresponding white paper listed below:
- [Shelley's ledger white paper](https://github.com/input-output-hk/cardano-ledger/releases/latest/download/shelley-ledger.pdf)
@ -8,7 +8,7 @@ This document covers the Shelley era, including its Allegra and Mary hard forks.
- **Scripts**:
- ***Script*** is the set of all possible native scripts.
- **Transactions**:
- ***Tx*** is the set of ShelleyMA transactions, composed of a transaction body and the set of witnesses.
- ***Tx*** is the set of ShelleyMA transactions, composed of a transaction body and a set of witnesses.
- ***TxBody*** is the type of ShelleyMA transaction bodies. Each transaction body is composed of a set of inputs and a list of outputs.
- ***txBody(tx)*** is the transaction body of the transaction.
- ***TxOut = Addr x TA*** is the set of transaction outputs, where
@ -21,18 +21,18 @@ This document covers the Shelley era, including its Allegra and Mary hard forks.
- ***Ix = *** is the set of indices (used to refer to a specific transaction output).
- ***txIns(txBody) ∈ P(TxIn)*** gives the set of transaction inputs of the transaction.
- ***utxo : TxIn → TxOut*** is a (partial) map that gives the unspent transaction output (UTxO) associated with a transaction input.
- Given ***A ⊆ dom(utxo)***, we will write ***A ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom utxo: utxo(ti) = to}***. Thus, we will write ***txIns(tx) ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom(utxo): utxo(ti) = to}*** to express the set of unspent transaction outputs associated with the set of transaction inputs of the transaction ***tx***.
- Given ***A ⊆ dom(utxo)***, we will write ***A ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom utxo: utxo(ti) = to}***. For example, we will write ***txIns(tx) ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom(utxo): utxo(ti) = to}*** to express the set of unspent transaction outputs associated with the set of transaction inputs of the transaction ***tx***.
- ***txTTL(txBody) ∈ Slot*** is the time-to-live of the transaction.
- ***txSize(Tx) ∈ *** gives the size of the transaction.
- ***fee(txBody) ∈ *** gives the fee paid by a transaction.
- ***minted(txBody)*** is the multiasset value minted (or burned) in the transaction.
- ***txInsScript(txBody) ⊆ P(TxIn)*** is the list of script inputs in the transaction body.
- ***txSize(txBody) ∈ *** is the size of the transaction in bytes.
- ***fee(txBody) ∈ *** is the fee paid by a transaction.
- ***minted(txBody)*** is the multi-asset value minted (or burned) in the transaction.
- ***txScripts(txBody) ⊆ P(TxIn)*** is the list of native scripts involved in the transaction.
- ***consumed(pps, utxo, txBody) ∈ *** is the *consumed value* of the transaction.
- In Shelley and Allegra, this equals the sum of all lovelace in the transaction inputs.
- In Mary, this equals the sum of all multiasset values in the transaction inputs.
- In Mary, this equals the sum of all multi-asset values in the transaction inputs.
- ***produced(pps, txBody) ∈ *** is the *produced value* of the transaction.
- In Shelley and Allegra, this equals the sum of all lovelace in the transaction outputs plus the transaction fee.
- In Mary, this equals the sum of all multiasset values in the outputs of the transaction plus the transaction fee plus the minted value.
- In Mary, this equals the sum of all multi-asset values in the outputs of the transaction plus the transaction fee plus the minted value.
- **Transaction metadata**:
- ***txMD(tx)*** is the metadata of the transaction.
- ***txMDHash(txBody)*** is the metadata hash contained within the transaction body.
@ -60,7 +60,7 @@ This document covers the Shelley era, including its Allegra and Mary hard forks.
- ***SKey*** is the set of signing keys (a.k.a. private keys).
- ***Sig*** is the set of signatures (i.e., the result of signing a byte array using a signing key).
- ***sig : SKey x Bytes -> Sig*** is the signing function.
- ***verify : VKey x Sig x Bytes -> Bool*** assesses whether the verification key applied to the signature yields the byte array as expected.
- ***verify : VKey x Sig x Bytes -> Bool*** assesses whether the result of applying the verification key to the signature equals the byte array parameter.
- The assumption is that if ***sk*** and ***vk*** are, respectively, a pair of secret and verification keys associated with one another. Thus, if ***sig(sk, d) = σ***, then it must be that ***verify(vk, σ, d) = true***.
- ***txVKWits(tx) ∈ P(VKey x Sig)*** gives the list of pairs of verification keys and signatures of the transaction.
- ***txScriptWits(tx) ⊆ P(Script)*** is the set of script witnesses of the transaction.
@ -86,10 +86,10 @@ Let ***tx ∈ Tx*** be a ShelleyMA transaction whose body is ***txBody ∈ TxBod
- **The preservation of value property holds**: Assuming no staking or delegation actions are involved, this property takes one of the two forms below:
- In Shelley and Allegra, the equation for the preservation of value is
<code>consumed(pps, utxo, txBody) = produced(pps, poolParams, txBody) + fee(txBody)</code>,
<code>consumed(pps, utxo, txBody) = produced(pps, txBody) + fee(txBody)</code>,
- In Mary, the equation is:
<code>consumed(pps, utxo, txBody) = produced(pps, poolParams, txBody) + fee(txBody) + minted(txBody) </code>,
<code>consumed(pps, utxo, txBody) = produced(pps, txBody) + fee(txBody) + minted(txBody) </code>
- **The fee paid by the transaction has to be greater than or equal to the minimum fee**:
<code>fee(txBody) ≥ minFees(pps, tx)</code>
@ -105,4 +105,4 @@ Let ***tx ∈ Tx*** be a ShelleyMA transaction whose body is ***txBody ∈ TxBod
- <code>paymentCredential<sub>utxo</sub>(txIn) = hash(vk)</code>
- **Script witnesses**: Each script address has a corresponding witness:
<code>∀ (script_hash, _) ∈ txInsScript(txBody) ◁ utxo : ∃ script ∈ txScriptWits(tx): hash(script) = script_hash</code>
<code>∀ (script_hash, _) ∈ txScripts(txBody) ◁ utxo : ∃ script ∈ txScriptWits(tx): hash(script) = script_hash</code>

View file

@ -0,0 +1,944 @@
//! Utilities required for Shelley-era transaction validation.
use crate::utils::{
add_minted_value, add_values, empty_value, extract_auxiliary_data, get_alonzo_comp_tx_size,
get_lovelace_from_alonzo_val, get_network_id_value, get_payment_part, get_shelley_address,
get_val_size_in_words, mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
AlonzoError::*,
AlonzoProtParams, FeePolicy, UTxOs,
ValidationError::{self, *},
ValidationResult,
};
use hex;
use pallas_addresses::{ScriptHash, ShelleyAddress, ShelleyPaymentPart};
use pallas_codec::{
minicbor::{encode, Encoder},
utils::{Bytes, KeepRaw},
};
use pallas_crypto::hash::Hash;
use pallas_primitives::{
alonzo::{
AddrKeyhash, Mint, MintedTx, MintedWitnessSet, NativeScript, PlutusData, PlutusScript,
PolicyId, Redeemer, RedeemerPointer, RedeemerTag, RequiredSigners, TransactionBody,
TransactionInput, TransactionOutput, VKeyWitness, Value,
},
byron::TxOut,
};
use pallas_traverse::{MultiEraInput, MultiEraOutput, OriginalHash};
use std::ops::Deref;
pub fn validate_alonzo_tx(
mtx: &MintedTx,
utxos: &UTxOs,
prot_pps: &AlonzoProtParams,
block_slot: &u64,
network_id: &u8,
) -> ValidationResult {
let tx_body: &TransactionBody = &mtx.transaction_body;
let size: &u64 = &get_alonzo_comp_tx_size(tx_body).ok_or(Alonzo(UnknownTxSize))?;
check_ins_not_empty(tx_body)?;
check_ins_and_collateral_in_utxos(tx_body, utxos)?;
check_tx_validity_interval(tx_body, mtx, block_slot)?;
check_fee(tx_body, size, mtx, utxos, prot_pps)?;
check_preservation_of_value(tx_body, utxos)?;
check_min_lovelace(tx_body, prot_pps)?;
check_output_val_size(tx_body, prot_pps)?;
check_network_id(tx_body, network_id)?;
check_tx_size(size, prot_pps)?;
check_tx_ex_units(mtx, prot_pps)?;
check_witness_set(mtx, utxos)?;
check_languages(mtx, prot_pps)?;
check_metadata(tx_body, mtx)?;
check_script_data_hash(tx_body, mtx)?;
check_minting(tx_body, mtx)
}
// The set of transaction inputs is not empty.
fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
if tx_body.inputs.is_empty() {
return Err(Alonzo(TxInsEmpty));
}
Ok(())
}
// All transaction inputs and collateral inputs are in the set of (yet) unspent
// transaction outputs.
fn check_ins_and_collateral_in_utxos(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
for input in tx_body.inputs.iter() {
if !(utxos.contains_key(&MultiEraInput::from_alonzo_compatible(input))) {
return Err(Alonzo(InputNotInUTxO));
}
}
match &tx_body.collateral {
None => Ok(()),
Some(collaterals) => {
for collateral in collaterals {
if !(utxos.contains_key(&MultiEraInput::from_alonzo_compatible(collateral))) {
return Err(Alonzo(CollateralNotInUTxO));
}
}
Ok(())
}
}
}
// The block slot is contained in the transaction validity interval, and the
// upper bound is translatable to UTC time.
fn check_tx_validity_interval(
tx_body: &TransactionBody,
mtx: &MintedTx,
block_slot: &u64,
) -> ValidationResult {
check_lower_bound(tx_body, block_slot)?;
check_upper_bound(tx_body, mtx, block_slot)
}
// If defined, the lower bound of the validity time interval does not exceed the
// block slot.
fn check_lower_bound(tx_body: &TransactionBody, block_slot: &u64) -> ValidationResult {
match tx_body.validity_interval_start {
Some(lower_bound) => {
if *block_slot < lower_bound {
Err(Alonzo(BlockPrecedesValInt))
} else {
Ok(())
}
}
None => Ok(()),
}
}
// If defined, the upper bound of the validity time interval is not exceeded by
// the block slot, and it is translatable to UTC time.
fn check_upper_bound(
tx_body: &TransactionBody,
_mtx: &MintedTx,
block_slot: &u64,
) -> ValidationResult {
match tx_body.ttl {
Some(upper_bound) => {
if upper_bound < *block_slot {
Err(Alonzo(BlockExceedsValInt))
} else {
// TODO: check that `upper_bound` is translatable to UTC time.
Ok(())
}
}
None => Ok(()),
}
}
fn check_fee(
tx_body: &TransactionBody,
size: &u64,
mtx: &MintedTx,
utxos: &UTxOs,
prot_pps: &AlonzoProtParams,
) -> ValidationResult {
check_min_fee(tx_body, size, prot_pps)?;
if presence_of_plutus_scripts(mtx) {
check_collaterals(tx_body, utxos, prot_pps)?
}
Ok(())
}
// The fee paid by the transaction should be greater than or equal to the
// minimum fee.
fn check_min_fee(
tx_body: &TransactionBody,
size: &u64,
prot_pps: &AlonzoProtParams,
) -> ValidationResult {
let fee_policy: &FeePolicy = &prot_pps.fee_policy;
if tx_body.fee < fee_policy.summand + fee_policy.multiplier * size {
return Err(Alonzo(FeeBelowMin));
}
Ok(())
}
fn presence_of_plutus_scripts(mtx: &MintedTx) -> bool {
let minted_witness_set: &MintedWitnessSet = &mtx.transaction_witness_set;
match &minted_witness_set.plutus_script {
Some(plutus_scripts) => !plutus_scripts.is_empty(),
None => false,
}
}
fn check_collaterals(
tx_body: &TransactionBody,
utxos: &UTxOs,
prot_pps: &AlonzoProtParams,
) -> ValidationResult {
let collaterals: &Vec<TransactionInput> = &tx_body
.collateral
.clone()
.ok_or(Alonzo(CollateralMissing))?;
check_collaterals_number(collaterals, prot_pps)?;
check_collaterals_address(collaterals, utxos)?;
check_collaterals_assets(tx_body, utxos, prot_pps)
}
// The set of collateral inputs is not empty.
// The number of collateral inputs is below maximum allowed by protocol.
fn check_collaterals_number(
collaterals: &[TransactionInput],
prot_pps: &AlonzoProtParams,
) -> ValidationResult {
let number_collateral: u64 = collaterals.len() as u64;
if number_collateral == 0 {
Err(Alonzo(CollateralMissing))
} else if number_collateral > prot_pps.max_collateral_inputs {
Err(Alonzo(TooManyCollaterals))
} else {
Ok(())
}
}
// Each collateral input refers to a verification-key address.
fn check_collaterals_address(collaterals: &[TransactionInput], utxos: &UTxOs) -> ValidationResult {
for collateral in collaterals {
match utxos.get(&MultiEraInput::from_alonzo_compatible(collateral)) {
Some(multi_era_output) => {
if let Some(alonzo_comp_output) = MultiEraOutput::as_alonzo(multi_era_output) {
if let ShelleyPaymentPart::Script(_) =
get_payment_part(alonzo_comp_output).ok_or(Alonzo(InputDecoding))?
{
return Err(Alonzo(CollateralNotVKeyLocked));
}
}
}
None => return Err(Alonzo(CollateralNotInUTxO)),
};
}
Ok(())
}
// Collateral inputs contain only lovelace, and in a number not lower than the
// minimum allowed.
fn check_collaterals_assets(
tx_body: &TransactionBody,
utxos: &UTxOs,
prot_pps: &AlonzoProtParams,
) -> ValidationResult {
let fee_percentage: u64 = tx_body.fee * prot_pps.collateral_percent;
match &tx_body.collateral {
Some(collaterals) => {
for collateral in collaterals {
match utxos.get(&MultiEraInput::from_alonzo_compatible(collateral)) {
Some(multi_era_output) => match MultiEraOutput::as_alonzo(multi_era_output) {
Some(TransactionOutput {
amount: Value::Coin(n),
..
}) => {
if *n * 100 < fee_percentage {
return Err(Alonzo(CollateralMinLovelace));
}
}
Some(TransactionOutput {
amount: Value::Multiasset(n, multi_assets),
..
}) => {
if *n * 100 < fee_percentage {
return Err(Alonzo(CollateralMinLovelace));
}
if !multi_assets.is_empty() {
return Err(Alonzo(NonLovelaceCollateral));
}
}
None => (),
},
None => return Err(Alonzo(CollateralNotInUTxO)),
}
}
}
None => return Err(Alonzo(CollateralMissing)),
}
Ok(())
}
// The preservation of value property holds.
fn check_preservation_of_value(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
let neg_val_err: ValidationError = Alonzo(NegativeValue);
let input: Value = get_consumed(tx_body, utxos)?;
let produced: Value = get_produced(tx_body)?;
let output: Value = add_values(&produced, &Value::Coin(tx_body.fee), &neg_val_err)?;
if let Some(m) = &tx_body.mint {
add_minted_value(&output, m, &neg_val_err)?;
}
if !values_are_equal(&input, &output) {
return Err(Alonzo(PreservationOfValue));
}
Ok(())
}
fn get_consumed(tx_body: &TransactionBody, utxos: &UTxOs) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = Alonzo(NegativeValue);
let mut res: Value = empty_value();
for input in tx_body.inputs.iter() {
let utxo_value: &MultiEraOutput = utxos
.get(&MultiEraInput::from_alonzo_compatible(input))
.ok_or(Alonzo(InputNotInUTxO))?;
match MultiEraOutput::as_alonzo(utxo_value) {
Some(TransactionOutput { amount, .. }) => res = add_values(&res, amount, &neg_val_err)?,
None => match MultiEraOutput::as_byron(utxo_value) {
Some(TxOut { amount, .. }) => {
res = add_values(&res, &Value::Coin(*amount), &neg_val_err)?
}
_ => return Err(Alonzo(InputNotInUTxO)),
},
}
}
Ok(res)
}
fn get_produced(tx_body: &TransactionBody) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = Alonzo(NegativeValue);
let mut res: Value = empty_value();
for TransactionOutput { amount, .. } in tx_body.outputs.iter() {
res = add_values(&res, amount, &neg_val_err)?;
}
Ok(res)
}
// All transaction outputs should contain at least the minimum lovelace.
fn check_min_lovelace(tx_body: &TransactionBody, prot_pps: &AlonzoProtParams) -> ValidationResult {
for output in tx_body.outputs.iter() {
if get_lovelace_from_alonzo_val(&output.amount) < compute_min_lovelace(output, prot_pps) {
return Err(Alonzo(MinLovelaceUnreached));
}
}
Ok(())
}
fn compute_min_lovelace(output: &TransactionOutput, prot_pps: &AlonzoProtParams) -> u64 {
let utxo_entry_size: u64 = get_val_size_in_words(&output.amount)
+ match output.datum_hash {
Some(_) => 37, // utxoEntrySizeWithoutVal (27) + dataHashSize (10)
None => 27, // utxoEntrySizeWithoutVal
};
prot_pps.coins_per_utxo_word * utxo_entry_size
}
// The size of the value in each of the outputs should not be greater than the
// maximum allowed.
fn check_output_val_size(
tx_body: &TransactionBody,
prot_pps: &AlonzoProtParams,
) -> ValidationResult {
for output in tx_body.outputs.iter() {
if get_val_size_in_words(&output.amount) > prot_pps.max_val_size {
return Err(Alonzo(MaxValSizeExceeded));
}
}
Ok(())
}
// The network ID of the transaction and its output addresses is correct.
fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
check_tx_outs_network_id(tx_body, network_id)?;
check_tx_network_id(tx_body, network_id)
}
// The network ID of each output matches the global network ID.
fn check_tx_outs_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
for output in tx_body.outputs.iter() {
let addr: ShelleyAddress =
get_shelley_address(Bytes::deref(&output.address)).ok_or(Alonzo(AddressDecoding))?;
if addr.network().value() != *network_id {
return Err(Alonzo(OutputWrongNetworkID));
}
}
Ok(())
}
// The network ID of the transaction body is either undefined or equal to the
// global network ID.
fn check_tx_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
if let Some(tx_network_id) = tx_body.network_id {
if get_network_id_value(tx_network_id) != *network_id {
return Err(Alonzo(TxWrongNetworkID));
}
}
Ok(())
}
// The transaction size does not exceed the protocol limit.
fn check_tx_size(size: &u64, prot_pps: &AlonzoProtParams) -> ValidationResult {
if *size > prot_pps.max_tx_size {
return Err(Alonzo(MaxTxSizeExceeded));
}
Ok(())
}
// The number of execution units of the transaction should not exceed the
// maximum allowed.
fn check_tx_ex_units(mtx: &MintedTx, prot_pps: &AlonzoProtParams) -> ValidationResult {
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
if presence_of_plutus_scripts(mtx) {
match &tx_wits.redeemer {
Some(redeemers_vec) => {
let mut steps: u64 = 0;
let mut mem: u32 = 0;
for Redeemer { ex_units, .. } in redeemers_vec {
mem += ex_units.mem;
steps += ex_units.steps;
}
if mem > prot_pps.max_tx_ex_mem || steps > prot_pps.max_tx_ex_steps {
return Err(Alonzo(TxExUnitsExceeded));
}
}
None => return Err(Alonzo(RedeemerMissing)),
}
}
Ok(())
}
fn check_witness_set(mtx: &MintedTx, utxos: &UTxOs) -> ValidationResult {
let tx_hash: &Vec<u8> = &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<Vec<VKeyWitness>> = &tx_wits.vkeywitness;
let native_scripts: Vec<NativeScript> = match &tx_wits.native_script {
Some(scripts) => scripts.clone().iter().map(|x| x.clone().unwrap()).collect(),
None => Vec::new(),
};
let plutus_scripts: Vec<PlutusScript> = match &tx_wits.plutus_script {
Some(scripts) => scripts.clone(),
None => Vec::new(),
};
check_needed_scripts_are_included(tx_body, utxos, &native_scripts, &plutus_scripts)?;
check_datums(tx_body, utxos, &tx_wits.plutus_data)?;
check_redeemers(tx_body, tx_wits, utxos)?;
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
// scripts needed to validate the transaction) equals the set of scripts
// contained in the transaction witnesses set.
fn check_needed_scripts_are_included(
tx_body: &TransactionBody,
utxos: &UTxOs,
native_scripts: &[NativeScript],
plutus_scripts: &[PlutusScript],
) -> ValidationResult {
let mut native_scripts: Vec<(bool, NativeScript)> =
native_scripts.iter().map(|x| (false, x.clone())).collect();
let mut plutus_scripts: Vec<(bool, PlutusScript)> =
plutus_scripts.iter().map(|x| (false, x.clone())).collect();
check_script_inputs(tx_body, &mut native_scripts, &mut plutus_scripts, utxos)?;
check_minting_policies(tx_body, &mut native_scripts, &mut plutus_scripts)?;
for (native_script_covered, _) in native_scripts.iter() {
if !native_script_covered {
return Err(Alonzo(UnneededNativeScript));
}
}
for (plutus_script_covered, _) in native_scripts.iter() {
if !plutus_script_covered {
return Err(Alonzo(UnneededPlutusScript));
}
}
Ok(())
}
fn check_datums(
tx_body: &TransactionBody,
utxos: &UTxOs,
option_plutus_data: &Option<Vec<KeepRaw<PlutusData>>>,
) -> ValidationResult {
let mut plutus_data: Vec<(bool, &KeepRaw<PlutusData>)> = match option_plutus_data {
Some(plutus_data) => plutus_data.iter().map(|x| (false, x)).collect(),
None => Vec::new(),
};
check_input_datum_hash_in_witness_set(tx_body, utxos, &mut plutus_data)?;
check_datums_from_witness_set_in_inputs_or_outputs(tx_body, &plutus_data)
}
// Each datum hash in a Plutus script input matches the hash of a datum in the
// transaction witness set.
fn check_input_datum_hash_in_witness_set(
tx_body: &TransactionBody,
utxos: &UTxOs,
plutus_data: &mut [(bool, &KeepRaw<PlutusData>)],
) -> ValidationResult {
for input in &tx_body.inputs {
match utxos
.get(&MultiEraInput::from_alonzo_compatible(input))
.and_then(MultiEraOutput::as_alonzo)
{
Some(output) => {
if let Some(datum_hash) = output.datum_hash {
find_datum_hash(datum_hash, plutus_data)?
}
}
None => return Err(Alonzo(InputNotInUTxO)),
}
}
Ok(())
}
fn find_datum_hash(
datum_hash: Hash<32>,
plutus_data: &mut [(bool, &KeepRaw<PlutusData>)],
) -> ValidationResult {
for (found, datum) in plutus_data {
let computed_datum_hash = pallas_crypto::hash::Hasher::<256>::hash(datum.raw_cbor());
if datum_hash == computed_datum_hash {
*found = true;
return Ok(());
}
}
Err(Alonzo(DatumMissing))
}
// 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.
fn check_datums_from_witness_set_in_inputs_or_outputs(
tx_body: &TransactionBody,
plutus_data: &[(bool, &KeepRaw<PlutusData>)],
) -> ValidationResult {
for (found, datum) in plutus_data {
if !found {
find_datum(datum, &tx_body.outputs)?
}
}
Ok(())
}
fn find_datum(datum: &KeepRaw<PlutusData>, outputs: &[TransactionOutput]) -> ValidationResult {
for output in outputs {
if let Some(hash) = output.datum_hash {
if pallas_crypto::hash::Hasher::<256>::hash(datum.raw_cbor()) == hash {
return Ok(());
}
}
}
Err(Alonzo(UnneededDatum))
}
// The set of redeemers in the transaction witness set should match the set of
// Plutus scripts needed to validate the transaction.
fn check_redeemers(
tx_body: &TransactionBody,
tx_wits: &MintedWitnessSet,
utxos: &UTxOs,
) -> ValidationResult {
let redeemer_pointers: Vec<RedeemerPointer> = match &tx_wits.redeemer {
Some(redeemers) => redeemers
.iter()
.map(|x| RedeemerPointer {
tag: x.tag.clone(),
index: x.index,
})
.collect(),
None => Vec::new(),
};
let plutus_scripts: Vec<RedeemerPointer> =
mk_plutus_script_redeemer_pointers(tx_body, tx_wits, utxos);
redeemer_pointers_coincide(&redeemer_pointers, &plutus_scripts)
}
fn mk_plutus_script_redeemer_pointers(
tx_body: &TransactionBody,
tx_wits: &MintedWitnessSet,
utxos: &UTxOs,
) -> Vec<RedeemerPointer> {
match &tx_wits.plutus_script {
Some(plutus_scripts) => {
let sorted_inputs: Vec<TransactionInput> = sort_inputs(&tx_body.inputs);
let mut res: Vec<RedeemerPointer> = Vec::new();
for (index, input) in sorted_inputs.iter().enumerate() {
if let Some(script_hash) = get_script_hash_from_input(input, utxos) {
for plutus_script in plutus_scripts.iter() {
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
if script_hash == hashed_script {
res.push(RedeemerPointer {
tag: RedeemerTag::Spend,
index: index as u32,
})
}
}
}
}
match &tx_body.mint {
Some(minted_value) => {
let sorted_policies: Vec<PolicyId> = sort_policies(minted_value);
for (index, policy) in sorted_policies.iter().enumerate() {
for plutus_script in plutus_scripts.iter() {
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
if *policy == hashed_script {
res.push(RedeemerPointer {
tag: RedeemerTag::Mint,
index: index as u32,
})
}
}
}
}
None => (),
}
res
}
None => Vec::new(),
}
}
// Lexicographical sorting for inputs.
fn sort_inputs(unsorted_inputs: &[TransactionInput]) -> Vec<TransactionInput> {
let mut res: Vec<TransactionInput> = unsorted_inputs.to_owned();
res.sort();
res
}
// Lexicographical sorting for PolicyID's.
fn sort_policies(mint: &Mint) -> Vec<PolicyId> {
let mut res: Vec<PolicyId> = mint
.clone()
.to_vec()
.iter()
.map(|(policy_id, _)| *policy_id)
.collect();
res.sort();
res
}
fn redeemer_pointers_coincide(
redeemers: &[RedeemerPointer],
plutus_scripts: &[RedeemerPointer],
) -> ValidationResult {
for redeemer_pointer in redeemers {
if plutus_scripts.iter().all(|x| x != redeemer_pointer) {
return Err(Alonzo(UnneededRedeemer));
}
}
for ps_redeemer_pointer in plutus_scripts {
if redeemers.iter().all(|x| x != ps_redeemer_pointer) {
return Err(Alonzo(RedeemerMissing));
}
}
Ok(())
}
fn check_script_inputs(
tx_body: &TransactionBody,
native_scripts: &mut [(bool, NativeScript)],
plutus_scripts: &mut [(bool, PlutusScript)],
utxos: &UTxOs,
) -> ValidationResult {
let mut inputs: Vec<(bool, ScriptHash)> = get_script_hashes(tx_body, utxos);
for (input_script_covered, input_script_hash) in &mut inputs {
for (native_script_covered, native_script) in native_scripts.iter_mut() {
let hashed_script: PolicyId = compute_native_script_hash(native_script);
if *input_script_hash == hashed_script {
*input_script_covered = true;
*native_script_covered = true;
}
}
for (plutus_script_covered, plutus_script) in plutus_scripts.iter_mut() {
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
if *input_script_hash == hashed_script {
*input_script_covered = true;
*plutus_script_covered = true;
}
}
}
for (input_script_covered, _) in inputs {
if !input_script_covered {
return Err(Alonzo(ScriptWitnessMissing));
}
}
Ok(())
}
fn get_script_hashes(tx_body: &TransactionBody, utxos: &UTxOs) -> Vec<(bool, ScriptHash)> {
let mut res: Vec<(bool, ScriptHash)> = Vec::new();
for input in tx_body.inputs.iter() {
if let Some(script_hash) = get_script_hash_from_input(input, utxos) {
res.push((false, script_hash))
}
}
res
}
fn get_script_hash_from_input(input: &TransactionInput, utxos: &UTxOs) -> Option<ScriptHash> {
utxos
.get(&MultiEraInput::from_alonzo_compatible(input))
.and_then(MultiEraOutput::as_alonzo)
.and_then(get_payment_part)
.and_then(|payment_part| match payment_part {
ShelleyPaymentPart::Script(script_hash) => Some(script_hash),
_ => None,
})
}
fn check_minting_policies(
tx_body: &TransactionBody,
native_scripts: &mut [(bool, NativeScript)],
plutus_scripts: &mut [(bool, PlutusScript)],
) -> ValidationResult {
match &tx_body.mint {
None => Ok(()),
Some(minted_value) => {
let mut minting_policies: Vec<(bool, PolicyId)> =
minted_value.iter().map(|(pol, _)| (false, *pol)).collect();
for (policy_covered, policy) in &mut minting_policies {
for (native_script_covered, native_script) in native_scripts.iter_mut() {
let hashed_script: PolicyId = compute_native_script_hash(native_script);
if *policy == hashed_script {
*policy_covered = true;
*native_script_covered = true;
}
}
for (plutus_script_covered, plutus_script) in plutus_scripts.iter_mut() {
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
if *policy == hashed_script {
*policy_covered = true;
*plutus_script_covered = true;
}
}
}
for (policy_covered, _) in minting_policies {
if !policy_covered {
return Err(Alonzo(MintingLacksPolicy));
}
}
Ok(())
}
}
}
fn compute_native_script_hash(script: &NativeScript) -> PolicyId {
let mut payload = Vec::new();
let _ = encode(script, &mut payload);
payload.insert(0, 0);
pallas_crypto::hash::Hasher::<224>::hash(&payload)
}
fn compute_plutus_script_hash(script: &PlutusScript) -> PolicyId {
let mut payload: Vec<u8> = Vec::from(script.as_ref());
payload.insert(0, 1);
pallas_crypto::hash::Hasher::<224>::hash(&payload)
}
// The owner of each transaction input and each collateral input should have
// signed the transaction.
fn check_vkey_input_wits(
mtx: &MintedTx,
vkey_wits: &Option<Vec<VKeyWitness>>,
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(VKWitnessMissing))?;
let tx_hash: &Vec<u8> = &Vec::from(mtx.transaction_body.original_hash().as_ref());
let mut inputs_and_collaterals: Vec<TransactionInput> = 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).ok_or(Alonzo(InputDecoding))? {
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) // required for native scripts
}
fn check_vk_wit(
payment_key_hash: &AddrKeyhash,
wits: &mut [(bool, VKeyWitness)],
data_to_verify: &[u8],
) -> ValidationResult {
for (vkey_wit_covered, 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 {
*vkey_wit_covered = true;
return Ok(());
}
}
}
Err(Alonzo(VKWitnessMissing))
}
fn check_remaining_vk_wits(
wits: &mut [(bool, VKeyWitness)],
data_to_verify: &[u8],
) -> ValidationResult {
for (covered, vkey_wit) in wits {
if !*covered {
if verify_signature(vkey_wit, data_to_verify) {
return Ok(());
} else {
return Err(Alonzo(VKWrongSignature));
}
}
}
Ok(())
}
// All required signers (needed by a Plutus script) have a corresponding match
// in the transaction witness set.
fn check_required_signers(
required_signers: &Option<RequiredSigners>,
vkey_wits: &Option<Vec<VKeyWitness>>,
data_to_verify: &[u8],
) -> ValidationResult {
if let Some(req_signers) = &required_signers {
match &vkey_wits {
Some(vkey_wits) => {
for req_signer in req_signers {
find_and_check_req_signer(req_signer, vkey_wits, data_to_verify)?
}
}
None => return Err(Alonzo(ReqSignerMissing)),
}
}
Ok(())
}
// Try to find the verification key in the witnesses, and verify the signature.
fn find_and_check_req_signer(
vkey_hash: &AddrKeyhash,
vkey_wits: &[VKeyWitness],
data_to_verify: &[u8],
) -> ValidationResult {
for vkey_wit in vkey_wits {
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(());
}
}
}
Err(Alonzo(ReqSignerMissing))
}
// The required script languages are included in the protocol parameters.
fn check_languages(_mtx: &MintedTx, _prot_pps: &AlonzoProtParams) -> ValidationResult {
Ok(())
}
// The metadata of the transaction is valid.
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
// and datums of the transaction witness set.
fn check_script_data_hash(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
match tx_body.script_data_hash {
Some(script_data_hash) => match (
&mtx.transaction_witness_set.plutus_data,
&mtx.transaction_witness_set.redeemer,
) {
(Some(plutus_data), Some(redeemer)) => {
let plutus_data: Vec<PlutusData> = plutus_data
.iter()
.map(|x| KeepRaw::unwrap(x.clone()))
.collect();
if script_data_hash == compute_script_integrity_hash(&plutus_data, redeemer) {
Ok(())
} else {
Err(Alonzo(ScriptIntegrityHash))
}
}
(_, _) => Err(Alonzo(ScriptIntegrityHash)),
},
None => {
if option_vec_is_empty(&mtx.transaction_witness_set.plutus_data)
&& option_vec_is_empty(&mtx.transaction_witness_set.redeemer)
{
Ok(())
} else {
Err(Alonzo(ScriptIntegrityHash))
}
}
}
}
fn compute_script_integrity_hash(plutus_data: &[PlutusData], redeemer: &[Redeemer]) -> Hash<32> {
let mut value_to_hash: Vec<u8> = Vec::new();
// First, the Redeemer.
let _ = encode(redeemer, &mut value_to_hash);
// Next, the PlutusData.
let mut plutus_data_encoder: Encoder<Vec<u8>> = Encoder::new(Vec::new());
let _ = plutus_data_encoder.begin_array();
for single_plutus_data in plutus_data.iter() {
let _ = plutus_data_encoder.encode(single_plutus_data);
}
let _ = plutus_data_encoder.end();
value_to_hash.extend(plutus_data_encoder.writer().clone());
// Finally, the cost model.
value_to_hash.extend(cost_model_cbor());
pallas_crypto::hash::Hasher::<256>::hash(&value_to_hash)
}
fn cost_model_cbor() -> Vec<u8> {
hex::decode(
"a141005901d59f1a000302590001011a00060bc719026d00011a000249f01903e800011a000249f018201a0025cea81971f70419744d186419744d186419744d186419744d186419744d186419744d18641864186419744d18641a000249f018201a000249f018201a000249f018201a000249f01903e800011a000249f018201a000249f01903e800081a000242201a00067e2318760001011a000249f01903e800081a000249f01a0001b79818f7011a000249f0192710011a0002155e19052e011903e81a000249f01903e8011a000249f018201a000249f018201a000249f0182001011a000249f0011a000249f0041a000194af18f8011a000194af18f8011a0002377c190556011a0002bdea1901f1011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000242201a00067e23187600010119f04c192bd200011a000249f018201a000242201a00067e2318760001011a000242201a00067e2318760001011a0025cea81971f704001a000141bb041a000249f019138800011a000249f018201a000302590001011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a00330da70101ff"
).unwrap()
}
fn option_vec_is_empty<T>(option_vec: &Option<Vec<T>>) -> bool {
match option_vec {
Some(vec) => vec.is_empty(),
None => true,
}
}
// Each minted / burned asset is paired with an appropriate native script or
// Plutus script.
fn check_minting(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
match &tx_body.mint {
Some(minted_value) => {
let native_script_wits: Vec<NativeScript> =
match &mtx.transaction_witness_set.native_script {
None => Vec::new(),
Some(keep_raw_native_script_wits) => keep_raw_native_script_wits
.iter()
.map(|x| x.clone().unwrap())
.collect(),
};
let plutus_script_wits: Vec<PlutusScript> = Vec::new();
for (policy, _) in minted_value.iter() {
if native_script_wits
.iter()
.all(|native_script| compute_native_script_hash(native_script) != *policy)
&& plutus_script_wits
.iter()
.all(|plutus_script| compute_plutus_script_hash(plutus_script) != *policy)
{
return Err(Alonzo(MintingLacksPolicy));
}
}
Ok(())
}
None => Ok(()),
}
}

View file

@ -2,9 +2,9 @@
use std::borrow::Cow;
use crate::types::{
use crate::utils::{
ByronError::*,
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs,
ByronProtParams, UTxOs,
ValidationError::{self, *},
ValidationResult,
};
@ -23,7 +23,7 @@ use pallas_crypto::{
use pallas_primitives::byron::{
Address, MintedTxPayload, PubKey, Signature as ByronSignature, Twit, Tx, TxIn, TxOut,
};
use pallas_traverse::OriginalHash;
use pallas_traverse::{MultiEraInput, MultiEraOutput, OriginalHash};
pub fn validate_byron_tx(
mtxp: &MintedTxPayload,
@ -251,11 +251,11 @@ fn get_data_to_verify(
let mut enc: Encoder<&mut Vec<u8>> = Encoder::new(buff);
match sign {
TaggedSignature::PkWitness(_) => {
enc.encode(SigningTag::Tx as u64)
enc.encode(1u64)
.map_err(|_| Byron(UnableToProcessWitness))?;
}
TaggedSignature::RedeemWitness(_) => {
enc.encode(SigningTag::RedeemTx as u64)
enc.encode(2u64)
.map_err(|_| Byron(UnableToProcessWitness))?;
}
}

View file

@ -1,40 +1,43 @@
//! Logic for validating and applying new blocks and txs to the chain state
pub mod alonzo;
pub mod byron;
pub mod shelley_ma;
pub mod types;
pub mod utils;
use alonzo::validate_alonzo_tx;
use byron::validate_byron_tx;
use pallas_traverse::{Era, MultiEraTx};
use shelley_ma::validate_shelley_ma_tx;
pub use types::{
pub use utils::{
Environment, MultiEraProtParams, UTxOs, ValidationError::TxAndProtParamsDiffer,
ValidationResult,
};
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
match env {
Environment {
prot_params: MultiEraProtParams::Byron(bpp),
prot_magic,
..
} => match metx {
MultiEraTx::Byron(mtxp) => validate_byron_tx(mtxp, utxos, bpp, prot_magic),
match env.prot_params() {
MultiEraProtParams::Byron(bpp) => match metx {
MultiEraTx::Byron(mtxp) => validate_byron_tx(mtxp, utxos, bpp, env.prot_magic()),
_ => Err(TxAndProtParamsDiffer),
},
Environment {
prot_params: MultiEraProtParams::Shelley(spp),
block_slot,
network_id,
..
} => match metx.era() {
Era::Shelley | Era::Allegra | Era::Mary => match metx.as_alonzo() {
Some(mtx) => {
validate_shelley_ma_tx(mtx, utxos, spp, block_slot, network_id, &metx.era())
}
None => Err(TxAndProtParamsDiffer),
MultiEraProtParams::Shelley(spp) => match metx {
MultiEraTx::AlonzoCompatible(mtx, Era::Shelley)
| MultiEraTx::AlonzoCompatible(mtx, Era::Allegra)
| MultiEraTx::AlonzoCompatible(mtx, Era::Mary) => validate_shelley_ma_tx(
mtx,
utxos,
spp,
env.block_slot(),
env.network_id(),
&metx.era(),
),
_ => Err(TxAndProtParamsDiffer),
},
MultiEraProtParams::Alonzo(app) => match metx {
MultiEraTx::AlonzoCompatible(mtx, Era::Alonzo) => {
validate_alonzo_tx(mtx, utxos, app, env.block_slot(), env.network_id())
}
_ => Err(TxAndProtParamsDiffer),
},
}

View file

@ -1,29 +1,26 @@
//! Utilities required for Shelley-era transaction validation.
//! Utilities required for ShelleyMA-era transaction validation.
use crate::types::{
FeePolicy,
use crate::utils::{
add_minted_value, add_values, empty_value, extract_auxiliary_data, get_alonzo_comp_tx_size,
get_lovelace_from_alonzo_val, get_payment_part, get_shelley_address, get_val_size_in_words,
mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature, FeePolicy,
ShelleyMAError::*,
ShelleyProtParams, UTxOs,
ValidationError::{self, *},
ValidationResult,
};
use pallas_addresses::{Address, PaymentKeyHash, ScriptHash, ShelleyAddress, ShelleyPaymentPart};
use pallas_codec::{
minicbor::encode,
utils::{Bytes, KeepRaw, KeyValuePairs},
};
use pallas_crypto::key::ed25519::{PublicKey, Signature};
use pallas_addresses::{PaymentKeyHash, ScriptHash, ShelleyAddress, ShelleyPaymentPart};
use pallas_codec::minicbor::encode;
use pallas_primitives::{
alonzo::{
AssetName, AuxiliaryData, Coin, MintedTx, MintedWitnessSet, Multiasset, NativeScript,
PolicyId, TransactionBody, TransactionOutput, VKeyWitness, Value,
MintedTx, MintedWitnessSet, NativeScript, PolicyId, TransactionBody, TransactionOutput,
VKeyWitness, Value,
},
byron::TxOut,
};
use pallas_traverse::{ComputeHash, Era, MultiEraInput, MultiEraOutput};
use std::{collections::HashMap, ops::Deref};
use std::{cmp::max, ops::Deref};
// TODO: implement each of the validation rules.
pub fn validate_shelley_ma_tx(
mtx: &MintedTx,
utxos: &UTxOs,
@ -34,45 +31,23 @@ pub fn validate_shelley_ma_tx(
) -> ValidationResult {
let tx_body: &TransactionBody = &mtx.transaction_body;
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
let size: &u64 = &get_tx_size(tx_body)?;
let auxiliary_data_hash: &Option<Bytes> = &tx_body.auxiliary_data_hash;
let auxiliary_data: &Option<&[u8]> = &extract_auxiliary_data(mtx);
let minted_value: &Option<Multiasset<i64>> = &tx_body.mint;
let native_script_wits: &Option<Vec<NativeScript>> = &mtx
.transaction_witness_set
.native_script
.as_ref()
.map(|x| x.iter().map(|y| y.deref().clone()).collect());
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)?;
check_ttl(tx_body, block_slot)?;
check_size(size, prot_pps)?;
check_tx_size(size, prot_pps)?;
check_min_lovelace(tx_body, prot_pps, era)?;
check_preservation_of_value(tx_body, utxos, era)?;
check_fees(tx_body, size, &prot_pps.fee_policy)?;
check_fees(tx_body, size, prot_pps)?;
check_network_id(tx_body, network_id)?;
check_metadata(auxiliary_data_hash, auxiliary_data)?;
check_witnesses(tx_body, utxos, tx_wits)?;
check_minting(minted_value, native_script_wits)
}
fn get_tx_size(tx_body: &TransactionBody) -> Result<u64, ValidationError> {
let mut buff: Vec<u8> = Vec::new();
match encode(tx_body, &mut buff) {
Ok(()) => Ok(buff.len() as u64),
Err(_) => Err(Shelley(UnknownTxSize)),
}
}
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)
check_metadata(tx_body, mtx)?;
check_witnesses(tx_body, tx_wits, utxos)?;
check_minting(tx_body, mtx)
}
fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
if tx_body.inputs.is_empty() {
return Err(Shelley(TxInsEmpty));
return Err(ShelleyMA(TxInsEmpty));
}
Ok(())
}
@ -80,7 +55,7 @@ fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
fn check_ins_in_utxos(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
for input in tx_body.inputs.iter() {
if !(utxos.contains_key(&MultiEraInput::from_alonzo_compatible(input))) {
return Err(Shelley(InputNotInUTxO));
return Err(ShelleyMA(InputNotInUTxO));
}
}
Ok(())
@ -90,18 +65,18 @@ fn check_ttl(tx_body: &TransactionBody, block_slot: &u64) -> ValidationResult {
match tx_body.ttl {
Some(ttl) => {
if ttl < *block_slot {
Err(Shelley(TTLExceeded))
Err(ShelleyMA(TTLExceeded))
} else {
Ok(())
}
}
None => Err(Shelley(AlonzoCompNotShelley)),
None => Err(ShelleyMA(AlonzoCompNotShelley)),
}
}
fn check_size(size: &u64, prot_pps: &ShelleyProtParams) -> ValidationResult {
fn check_tx_size(size: &u64, prot_pps: &ShelleyProtParams) -> ValidationResult {
if *size > prot_pps.max_tx_size {
return Err(Shelley(MaxTxSizeExceeded));
return Err(ShelleyMA(MaxTxSizeExceeded));
}
Ok(())
}
@ -111,34 +86,46 @@ fn check_min_lovelace(
prot_pps: &ShelleyProtParams,
era: &Era,
) -> ValidationResult {
for TransactionOutput { amount, .. } in &tx_body.outputs {
match (era, amount) {
(Era::Shelley, Value::Coin(lovelace))
| (Era::Allegra, Value::Coin(lovelace))
| (Era::Mary, Value::Multiasset(lovelace, _)) => {
if *lovelace < prot_pps.min_lovelace {
return Err(Shelley(MinLovelaceUnreached));
for output in &tx_body.outputs {
match era {
Era::Shelley | Era::Allegra | Era::Mary => {
if get_lovelace_from_alonzo_val(&output.amount)
< compute_min_lovelace(output, prot_pps)
{
return Err(ShelleyMA(MinLovelaceUnreached));
}
}
_ => return Err(Shelley(ValueNotShelley)),
_ => return Err(ShelleyMA(ValueNotShelley)),
}
}
Ok(())
}
fn compute_min_lovelace(output: &TransactionOutput, prot_pps: &ShelleyProtParams) -> u64 {
match &output.amount {
Value::Coin(_) => prot_pps.min_lovelace,
Value::Multiasset(lovelace, _) => {
let utxo_entry_size: u64 = 27 + get_val_size_in_words(&output.amount);
let coins_per_utxo_word: u64 = prot_pps.min_lovelace / 27;
max(*lovelace, utxo_entry_size * coins_per_utxo_word)
}
}
}
fn check_preservation_of_value(
tx_body: &TransactionBody,
utxos: &UTxOs,
era: &Era,
) -> ValidationResult {
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
let input: Value = get_consumed(tx_body, utxos, era)?;
let produced: Value = get_produced(tx_body, era)?;
let output: Value = add_values(&produced, &Value::Coin(tx_body.fee))?;
let output: Value = add_values(&produced, &Value::Coin(tx_body.fee), &neg_val_err)?;
if let Some(m) = &tx_body.mint {
add_minted_value(&output, m)?;
add_minted_value(&output, m, &neg_val_err)?;
}
if !values_are_equal(&input, &output) {
return Err(Shelley(PreservationOfValue));
return Err(ShelleyMA(PreservationOfValue));
}
Ok(())
}
@ -148,20 +135,23 @@ fn get_consumed(
utxos: &UTxOs,
era: &Era,
) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
let mut res: Value = empty_value();
for input in tx_body.inputs.iter() {
let utxo_value: &MultiEraOutput = utxos
.get(&MultiEraInput::from_alonzo_compatible(input))
.ok_or(Shelley(InputNotInUTxO))?;
.ok_or(ShelleyMA(InputNotInUTxO))?;
match MultiEraOutput::as_alonzo(utxo_value) {
Some(TransactionOutput { amount, .. }) => match (amount, era) {
(Value::Coin(..), _) => res = add_values(&res, amount)?,
(Value::Multiasset(..), Era::Shelley) => return Err(Shelley(ValueNotShelley)),
_ => res = add_values(&res, amount)?,
(Value::Coin(..), _) => res = add_values(&res, amount, &neg_val_err)?,
(Value::Multiasset(..), Era::Shelley) => return Err(ShelleyMA(ValueNotShelley)),
_ => res = add_values(&res, amount, &neg_val_err)?,
},
None => match MultiEraOutput::as_byron(utxo_value) {
Some(TxOut { amount, .. }) => res = add_values(&res, &Value::Coin(*amount))?,
_ => return Err(Shelley(InputNotInUTxO)),
Some(TxOut { amount, .. }) => {
res = add_values(&res, &Value::Coin(*amount), &neg_val_err)?
}
_ => return Err(ShelleyMA(InputNotInUTxO)),
},
}
}
@ -169,237 +159,72 @@ fn get_consumed(
}
fn get_produced(tx_body: &TransactionBody, era: &Era) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
let mut res: Value = empty_value();
for TransactionOutput { amount, .. } in tx_body.outputs.iter() {
match (amount, era) {
(Value::Coin(..), _) => res = add_values(&res, amount)?,
(Value::Multiasset(..), Era::Shelley) => return Err(Shelley(WrongEraOutput)),
_ => res = add_values(&res, amount)?,
(Value::Coin(..), _) => res = add_values(&res, amount, &neg_val_err)?,
(Value::Multiasset(..), Era::Shelley) => return Err(ShelleyMA(WrongEraOutput)),
_ => res = add_values(&res, amount, &neg_val_err)?,
}
}
Ok(res)
}
fn empty_value() -> Value {
Value::Multiasset(0, Multiasset::<Coin>::from(Vec::new()))
}
fn add_values(first: &Value, second: &Value) -> Result<Value, ValidationError> {
match (first, second) {
(Value::Coin(f), Value::Coin(s)) => Ok(Value::Coin(f + s)),
(Value::Multiasset(f, fma), Value::Coin(s)) => Ok(Value::Multiasset(f + s, fma.clone())),
(Value::Coin(f), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(f + s, sma.clone())),
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(
f + s,
coerce_to_coin(&add_multiasset_values(
&coerce_to_i64(fma),
&coerce_to_i64(sma),
))?,
)),
}
}
fn add_minted_value(
base_value: &Value,
minted_value: &Multiasset<i64>,
) -> Result<Value, ValidationError> {
match base_value {
Value::Coin(n) => Ok(Value::Multiasset(*n, coerce_to_coin(minted_value)?)),
Value::Multiasset(n, mary_base_value) => Ok(Value::Multiasset(
*n,
coerce_to_coin(&add_multiasset_values(
&coerce_to_i64(mary_base_value),
minted_value,
))?,
)),
}
}
fn coerce_to_i64(value: &Multiasset<Coin>) -> Multiasset<i64> {
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, i64>)> = Vec::new();
for (policy, assets) in value.clone().to_vec().iter() {
let mut aa: Vec<(AssetName, i64)> = Vec::new();
for (asset_name, amount) in assets.clone().to_vec().iter() {
aa.push((asset_name.clone(), *amount as i64));
}
res.push((*policy, KeyValuePairs::<AssetName, i64>::from(aa)));
}
KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, i64>>::from(res)
}
fn coerce_to_coin(value: &Multiasset<i64>) -> Result<Multiasset<Coin>, ValidationError> {
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, Coin>)> = Vec::new();
for (policy, assets) in value.clone().to_vec().iter() {
let mut aa: Vec<(AssetName, Coin)> = Vec::new();
for (asset_name, amount) in assets.clone().to_vec().iter() {
if *amount < 0 {
return Err(Shelley(NegativeValue));
}
aa.push((asset_name.clone(), *amount as u64));
}
res.push((*policy, KeyValuePairs::<AssetName, Coin>::from(aa)));
}
Ok(KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, Coin>>::from(res))
}
fn add_multiasset_values(first: &Multiasset<i64>, second: &Multiasset<i64>) -> Multiasset<i64> {
let mut res: HashMap<PolicyId, HashMap<AssetName, i64>> = HashMap::new();
for (policy, new_assets) in first.iter() {
match res.get(policy) {
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
};
}
for (policy, new_assets) in second.iter() {
match res.get(policy) {
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
};
}
wrap_multiasset(res)
}
fn add_same_policy_assets(
old_assets: &HashMap<AssetName, i64>,
new_assets: &KeyValuePairs<AssetName, i64>,
) -> HashMap<AssetName, i64> {
let mut res: HashMap<AssetName, i64> = old_assets.clone();
for (asset_name, new_amount) in new_assets.iter() {
match res.get(asset_name) {
Some(old_amount) => res.insert(asset_name.clone(), old_amount + *new_amount),
None => res.insert(asset_name.clone(), *new_amount),
};
}
res
}
fn wrap_multiasset(input: HashMap<PolicyId, HashMap<AssetName, i64>>) -> Multiasset<i64> {
Multiasset::<i64>::from(
input
.into_iter()
.map(|(policy, assets)| {
(
policy,
KeyValuePairs::<AssetName, i64>::from(
assets.into_iter().collect::<Vec<(AssetName, i64)>>(),
),
)
})
.collect::<Vec<(PolicyId, KeyValuePairs<AssetName, i64>)>>(),
)
}
fn values_are_equal(first: &Value, second: &Value) -> bool {
match (first, second) {
(Value::Coin(f), Value::Coin(s)) => f == s,
(Value::Multiasset(..), Value::Coin(..)) => false,
(Value::Coin(..), Value::Multiasset(..)) => false,
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => {
if f != s {
false
} else {
for (fpolicy, fassets) in fma.iter() {
match find_policy(sma, fpolicy) {
Some(sassets) => {
for (fasset_name, famount) in fassets.iter() {
match find_assets(&sassets, fasset_name) {
Some(samount) => {
if *famount != samount {
return false;
}
}
None => return false,
};
}
}
None => return false,
}
}
true
}
}
}
}
fn find_policy(
mary_value: &Multiasset<Coin>,
search_policy: &PolicyId,
) -> Option<KeyValuePairs<AssetName, Coin>> {
for (policy, assets) in mary_value.clone().to_vec().iter() {
if policy == search_policy {
return Some(assets.clone());
}
}
None
}
fn find_assets(assets: &KeyValuePairs<AssetName, Coin>, asset_name: &AssetName) -> Option<Coin> {
for (an, amount) in assets.clone().to_vec().iter() {
if an == asset_name {
return Some(*amount);
}
}
None
}
fn check_fees(tx_body: &TransactionBody, size: &u64, fee_policy: &FeePolicy) -> ValidationResult {
fn check_fees(
tx_body: &TransactionBody,
size: &u64,
prot_pps: &ShelleyProtParams,
) -> ValidationResult {
let fee_policy: &FeePolicy = &prot_pps.fee_policy;
if tx_body.fee < fee_policy.summand + fee_policy.multiplier * size {
return Err(Shelley(FeesBelowMin));
return Err(ShelleyMA(FeesBelowMin));
}
Ok(())
}
fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
for output in tx_body.outputs.iter() {
let addr: ShelleyAddress = get_shelley_address(Vec::<u8>::from(output.address.clone()))?;
let addr: ShelleyAddress =
get_shelley_address(&output.address).ok_or(ShelleyMA(AddressDecoding))?;
if addr.network().value() != *network_id {
return Err(Shelley(WrongNetworkID));
return Err(ShelleyMA(WrongNetworkID));
}
}
Ok(())
}
fn get_shelley_address(address: Vec<u8>) -> Result<ShelleyAddress, ValidationError> {
match Address::from_bytes(&address) {
Ok(Address::Shelley(sa)) => Ok(sa),
Ok(_) => Err(Shelley(WrongEraOutput)),
Err(_) => Err(Shelley(AddressDecoding)),
}
}
fn check_metadata(
auxiliary_data_hash: &Option<Bytes>,
auxiliary_data_cbor: &Option<&[u8]>,
) -> ValidationResult {
match (auxiliary_data_hash, auxiliary_data_cbor) {
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(Shelley(MetadataHash))
Err(ShelleyMA(MetadataHash))
}
}
(None, None) => Ok(()),
_ => Err(Shelley(MetadataHash)),
_ => Err(ShelleyMA(MetadataHash)),
}
}
fn check_witnesses(
tx_body: &TransactionBody,
utxos: &UTxOs,
tx_wits: &MintedWitnessSet,
utxos: &UTxOs,
) -> ValidationResult {
let wits: &mut Vec<(bool, VKeyWitness)> = &mut mk_vkwitness_check_list(&tx_wits.vkeywitness)?;
let vk_wits: &mut Vec<(bool, VKeyWitness)> =
&mut mk_alonzo_vk_wits_check_list(&tx_wits.vkeywitness, ShelleyMA(MissingVKWitness))?;
let tx_hash: &Vec<u8> = &Vec::from(tx_body.compute_hash().as_ref());
for input in tx_body.inputs.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)? {
match get_payment_part(alonzo_comp_output).ok_or(ShelleyMA(AddressDecoding))? {
ShelleyPaymentPart::Key(payment_key_hash) => {
check_verification_key_witness(&payment_key_hash, tx_hash, wits)?
check_vk_wit(&payment_key_hash, tx_hash, vk_wits)?
}
ShelleyPaymentPart::Script(script_hash) => check_native_script_witness(
&script_hash,
@ -411,50 +236,28 @@ fn check_witnesses(
}
}
}
None => return Err(Shelley(InputNotInUTxO)),
None => return Err(ShelleyMA(InputNotInUTxO)),
}
}
check_remaining_verification_key_witnesses(wits, tx_hash)
check_remaining_vk_wits(vk_wits, tx_hash)
}
fn mk_vkwitness_check_list(
wits: &Option<Vec<VKeyWitness>>,
) -> Result<Vec<(bool, VKeyWitness)>, ValidationError> {
Ok(wits
.clone()
.ok_or(Shelley(MissingVKWitness))?
.iter()
.map(|x| (false, x.clone()))
.collect::<Vec<(bool, VKeyWitness)>>())
}
fn get_payment_part(tx_out: &TransactionOutput) -> Result<ShelleyPaymentPart, ValidationError> {
let addr: ShelleyAddress = get_shelley_address(Vec::<u8>::from(tx_out.address.clone()))?;
Ok(addr.payment().clone())
}
fn check_verification_key_witness(
fn check_vk_wit(
payment_key_hash: &PaymentKeyHash,
data_to_verify: &Vec<u8>,
wits: &mut Vec<(bool, VKeyWitness)>,
data_to_verify: &[u8],
wits: &mut [(bool, VKeyWitness)],
) -> ValidationResult {
for (found, VKeyWitness { vkey, signature }) in wits {
if pallas_crypto::hash::Hasher::<224>::hash(vkey) == *payment_key_hash {
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
public_key_source.copy_from_slice(vkey.as_slice());
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
signature_source.copy_from_slice(signature.as_slice());
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
if public_key.verify(data_to_verify, &sig) {
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) {
*found = true;
return Ok(());
} else {
return Err(Shelley(WrongSignature));
return Err(ShelleyMA(WrongSignature));
}
}
}
Err(Shelley(MissingVKWitness))
Err(ShelleyMA(MissingVKWitness))
}
fn check_native_script_witness(
@ -470,58 +273,51 @@ fn check_native_script_witness(
return Ok(());
}
}
Err(Shelley(MissingScriptWitness))
Err(ShelleyMA(MissingScriptWitness))
}
None => Err(Shelley(MissingScriptWitness)),
None => Err(ShelleyMA(MissingScriptWitness)),
}
}
fn check_remaining_verification_key_witnesses(
fn check_remaining_vk_wits(
wits: &mut Vec<(bool, VKeyWitness)>,
data_to_verify: &Vec<u8>,
data_to_verify: &[u8],
) -> ValidationResult {
for (covered, VKeyWitness { vkey, signature }) in wits {
for (covered, vkey_wit) in wits {
if !*covered {
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
public_key_source.copy_from_slice(vkey.as_slice());
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
signature_source.copy_from_slice(signature.as_slice());
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
if !public_key.verify(data_to_verify, &sig) {
return Err(Shelley(WrongSignature));
}
}
}
Ok(())
}
fn check_minting(
values: &Option<Multiasset<i64>>,
scripts: &Option<Vec<NativeScript>>,
) -> ValidationResult {
match (values, scripts) {
(None, _) => Ok(()),
(Some(_), None) => Err(Shelley(MintingLacksPolicy)),
(Some(minted_value), Some(native_script_wits)) => {
for (policy, _) in minted_value.iter() {
if check_policy(policy, native_script_wits) {
if verify_signature(vkey_wit, data_to_verify) {
return Ok(());
} else {
return Err(ShelleyMA(WrongSignature));
}
}
}
Ok(())
}
}
}
fn check_policy(policy: &PolicyId, native_script_wits: &[NativeScript]) -> bool {
for script in native_script_wits.iter() {
let hashed_script: PolicyId = compute_script_hash(script);
if *policy == hashed_script {
return true;
fn check_minting(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
match &tx_body.mint {
Some(minted_value) => {
let native_script_wits: Vec<NativeScript> =
match &mtx.transaction_witness_set.native_script {
None => Vec::new(),
Some(keep_raw_native_script_wits) => keep_raw_native_script_wits
.iter()
.map(|x| x.clone().unwrap())
.collect(),
};
for (policy, _) in minted_value.iter() {
if native_script_wits
.iter()
.all(|script| compute_script_hash(script) != *policy)
{
return Err(ShelleyMA(MintingLacksPolicy));
}
}
false
Ok(())
}
None => Ok(()),
}
}
fn compute_script_hash(script: &NativeScript) -> PolicyId {

View file

@ -1,98 +0,0 @@
//! Base types used for validating transactions in each era.
use std::collections::HashMap;
pub use pallas_traverse::{MultiEraInput, MultiEraOutput};
pub type UTxOs<'b> = HashMap<MultiEraInput<'b>, MultiEraOutput<'b>>;
#[derive(Debug, Clone)]
pub struct ByronProtParams {
pub fee_policy: FeePolicy,
pub max_tx_size: u64,
}
#[derive(Debug, Clone)]
pub struct ShelleyProtParams {
pub fee_policy: FeePolicy,
pub max_tx_size: u64,
pub min_lovelace: u64,
}
#[derive(Debug, Clone)]
pub struct FeePolicy {
pub summand: u64,
pub multiplier: u64,
}
// TODO: add variants for the other eras.
#[derive(Debug)]
#[non_exhaustive]
pub enum MultiEraProtParams {
Byron(ByronProtParams),
Shelley(ShelleyProtParams),
}
#[derive(Debug)]
pub struct Environment {
pub prot_params: MultiEraProtParams,
pub prot_magic: u32,
pub block_slot: u64,
pub network_id: u8,
}
#[non_exhaustive]
pub enum SigningTag {
Tx = 0x01,
RedeemTx = 0x02,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ValidationError {
TxAndProtParamsDiffer,
Byron(ByronError),
Shelley(ShelleyMAError),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ByronError {
TxInsEmpty,
TxOutsEmpty,
InputNotInUTxO,
OutputWithoutLovelace,
UnknownTxSize,
UnableToComputeFees,
FeesBelowMin,
MaxTxSizeExceeded,
UnableToProcessWitness,
MissingWitness,
WrongSignature,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ShelleyMAError {
TxInsEmpty,
InputNotInUTxO,
TTLExceeded,
AlonzoCompNotShelley,
UnknownTxSize,
MaxTxSizeExceeded,
ValueNotShelley,
MinLovelaceUnreached,
PreservationOfValue,
NegativeValue,
FeesBelowMin,
WrongEraOutput,
AddressDecoding,
WrongNetworkID,
MetadataHash,
MissingVKWitness,
MissingScriptWitness,
WrongSignature,
MintingLacksPolicy,
}
pub type ValidationResult = Result<(), ValidationError>;

View file

@ -0,0 +1,260 @@
//! Base types used for validating transactions in each era.
pub mod environment;
pub mod validation;
pub use environment::*;
use pallas_addresses::{Address, ShelleyAddress, ShelleyPaymentPart};
use pallas_codec::{
minicbor::encode,
utils::{Bytes, KeepRaw, KeyValuePairs},
};
use pallas_crypto::key::ed25519::{PublicKey, Signature};
use pallas_primitives::alonzo::{
AssetName, AuxiliaryData, Coin, MintedTx, Multiasset, NetworkId, PolicyId, TransactionBody,
TransactionOutput, VKeyWitness, Value,
};
use pallas_traverse::{MultiEraInput, MultiEraOutput};
use std::collections::HashMap;
use std::ops::Deref;
pub use validation::*;
pub type UTxOs<'b> = HashMap<MultiEraInput<'b>, MultiEraOutput<'b>>;
pub fn get_alonzo_comp_tx_size(tx_body: &TransactionBody) -> Option<u64> {
let mut buff: Vec<u8> = Vec::new();
match encode(tx_body, &mut buff) {
Ok(()) => Some(buff.len() as u64),
Err(_) => None,
}
}
pub fn empty_value() -> Value {
Value::Multiasset(0, Multiasset::<Coin>::from(Vec::new()))
}
pub fn add_values(
first: &Value,
second: &Value,
err: &ValidationError,
) -> Result<Value, ValidationError> {
match (first, second) {
(Value::Coin(f), Value::Coin(s)) => Ok(Value::Coin(f + s)),
(Value::Multiasset(f, fma), Value::Coin(s)) => Ok(Value::Multiasset(f + s, fma.clone())),
(Value::Coin(f), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(f + s, sma.clone())),
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(
f + s,
coerce_to_coin(
&add_multiasset_values(&coerce_to_i64(fma), &coerce_to_i64(sma)),
err,
)?,
)),
}
}
pub fn add_minted_value(
base_value: &Value,
minted_value: &Multiasset<i64>,
err: &ValidationError,
) -> Result<Value, ValidationError> {
match base_value {
Value::Coin(n) => Ok(Value::Multiasset(*n, coerce_to_coin(minted_value, err)?)),
Value::Multiasset(n, mary_base_value) => Ok(Value::Multiasset(
*n,
coerce_to_coin(
&add_multiasset_values(&coerce_to_i64(mary_base_value), minted_value),
err,
)?,
)),
}
}
fn coerce_to_i64(value: &Multiasset<Coin>) -> Multiasset<i64> {
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, i64>)> = Vec::new();
for (policy, assets) in value.clone().to_vec().iter() {
let mut aa: Vec<(AssetName, i64)> = Vec::new();
for (asset_name, amount) in assets.clone().to_vec().iter() {
aa.push((asset_name.clone(), *amount as i64));
}
res.push((*policy, KeyValuePairs::<AssetName, i64>::from(aa)));
}
KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, i64>>::from(res)
}
fn coerce_to_coin(
value: &Multiasset<i64>,
err: &ValidationError,
) -> Result<Multiasset<Coin>, ValidationError> {
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, Coin>)> = Vec::new();
for (policy, assets) in value.clone().to_vec().iter() {
let mut aa: Vec<(AssetName, Coin)> = Vec::new();
for (asset_name, amount) in assets.clone().to_vec().iter() {
if *amount < 0 {
return Err(err.clone());
}
aa.push((asset_name.clone(), *amount as u64));
}
res.push((*policy, KeyValuePairs::<AssetName, Coin>::from(aa)));
}
Ok(KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, Coin>>::from(res))
}
fn add_multiasset_values(first: &Multiasset<i64>, second: &Multiasset<i64>) -> Multiasset<i64> {
let mut res: HashMap<PolicyId, HashMap<AssetName, i64>> = HashMap::new();
for (policy, new_assets) in first.iter() {
match res.get(policy) {
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
};
}
for (policy, new_assets) in second.iter() {
match res.get(policy) {
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
};
}
wrap_multiasset(res)
}
fn add_same_policy_assets(
old_assets: &HashMap<AssetName, i64>,
new_assets: &KeyValuePairs<AssetName, i64>,
) -> HashMap<AssetName, i64> {
let mut res: HashMap<AssetName, i64> = old_assets.clone();
for (asset_name, new_amount) in new_assets.iter() {
match res.get(asset_name) {
Some(old_amount) => res.insert(asset_name.clone(), old_amount + *new_amount),
None => res.insert(asset_name.clone(), *new_amount),
};
}
res
}
fn wrap_multiasset(input: HashMap<PolicyId, HashMap<AssetName, i64>>) -> Multiasset<i64> {
Multiasset::<i64>::from(
input
.into_iter()
.map(|(policy, assets)| {
(
policy,
KeyValuePairs::<AssetName, i64>::from(
assets.into_iter().collect::<Vec<(AssetName, i64)>>(),
),
)
})
.collect::<Vec<(PolicyId, KeyValuePairs<AssetName, i64>)>>(),
)
}
pub fn values_are_equal(first: &Value, second: &Value) -> bool {
match (first, second) {
(Value::Coin(f), Value::Coin(s)) => f == s,
(Value::Multiasset(..), Value::Coin(..)) => false,
(Value::Coin(..), Value::Multiasset(..)) => false,
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => {
if f != s {
false
} else {
for (fpolicy, fassets) in fma.iter() {
match find_policy(sma, fpolicy) {
Some(sassets) => {
for (fasset_name, famount) in fassets.iter() {
match find_assets(&sassets, fasset_name) {
Some(samount) => {
if *famount != samount {
return false;
}
}
None => return false,
};
}
}
None => return false,
}
}
true
}
}
}
}
fn find_policy(
mary_value: &Multiasset<Coin>,
search_policy: &PolicyId,
) -> Option<KeyValuePairs<AssetName, Coin>> {
for (policy, assets) in mary_value.clone().to_vec().iter() {
if policy == search_policy {
return Some(assets.clone());
}
}
None
}
fn find_assets(assets: &KeyValuePairs<AssetName, Coin>, asset_name: &AssetName) -> Option<Coin> {
for (an, amount) in assets.clone().to_vec().iter() {
if an == asset_name {
return Some(*amount);
}
}
None
}
pub fn get_lovelace_from_alonzo_val(val: &Value) -> Coin {
match val {
Value::Coin(res) => *res,
Value::Multiasset(res, _) => *res,
}
}
pub fn get_network_id_value(network_id: NetworkId) -> u8 {
match network_id {
NetworkId::One => 1,
NetworkId::Two => 2,
}
}
pub fn mk_alonzo_vk_wits_check_list(
wits: &Option<Vec<VKeyWitness>>,
err: ValidationError,
) -> Result<Vec<(bool, VKeyWitness)>, ValidationError> {
Ok(wits
.clone()
.ok_or(err)?
.iter()
.map(|x| (false, x.clone()))
.collect::<Vec<(bool, VKeyWitness)>>())
}
pub fn verify_signature(vk_wit: &VKeyWitness, data_to_verify: &[u8]) -> bool {
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
public_key_source.copy_from_slice(vk_wit.vkey.as_slice());
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
signature_source.copy_from_slice(vk_wit.signature.as_slice());
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
public_key.verify(data_to_verify, &sig)
}
pub fn get_payment_part(tx_out: &TransactionOutput) -> Option<ShelleyPaymentPart> {
let addr: ShelleyAddress = get_shelley_address(Bytes::deref(&tx_out.address))?;
Some(addr.payment().clone())
}
pub fn get_shelley_address(address: &[u8]) -> Option<ShelleyAddress> {
match Address::from_bytes(address) {
Ok(Address::Shelley(sa)) => Some(sa),
_ => 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)
}
pub fn get_val_size_in_words(val: &Value) -> u64 {
let mut tx_buf: Vec<u8> = Vec::new();
let _ = encode(val, &mut tx_buf);
(tx_buf.len() as u64 + 7) / 8 // ceiling of the result of dividing
}

View file

@ -0,0 +1,70 @@
//! Types used for representing the environment required for validation in each
//! era.
#[derive(Debug)]
pub struct Environment {
pub prot_params: MultiEraProtParams,
pub prot_magic: u32,
pub block_slot: u64,
pub network_id: u8,
}
// TODO: add variants for the other eras.
#[derive(Debug)]
#[non_exhaustive]
pub enum MultiEraProtParams {
Byron(ByronProtParams),
Shelley(ShelleyProtParams),
Alonzo(AlonzoProtParams),
}
#[derive(Debug, Clone)]
pub struct ByronProtParams {
pub fee_policy: FeePolicy,
pub max_tx_size: u64,
}
#[derive(Debug, Clone)]
pub struct ShelleyProtParams {
pub fee_policy: FeePolicy,
pub max_tx_size: u64,
pub min_lovelace: u64,
}
#[derive(Debug, Clone)]
pub struct FeePolicy {
pub summand: u64,
pub multiplier: u64,
}
#[derive(Debug, Clone)]
pub struct AlonzoProtParams {
pub fee_policy: FeePolicy,
pub max_tx_size: u64,
pub max_block_ex_mem: u64,
pub max_block_ex_steps: u64,
pub max_tx_ex_mem: u32,
pub max_tx_ex_steps: u64,
pub max_val_size: u64,
pub collateral_percent: u64,
pub max_collateral_inputs: u64,
pub coins_per_utxo_word: u64,
}
impl Environment {
pub fn prot_params(&self) -> &MultiEraProtParams {
&self.prot_params
}
pub fn prot_magic(&self) -> &u32 {
&self.prot_magic
}
pub fn block_slot(&self) -> &u64 {
&self.block_slot
}
pub fn network_id(&self) -> &u8 {
&self.network_id
}
}

View file

@ -0,0 +1,94 @@
//! Types for validating transactions in each era.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ValidationError {
TxAndProtParamsDiffer,
Byron(ByronError),
ShelleyMA(ShelleyMAError),
Alonzo(AlonzoError),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ByronError {
TxInsEmpty,
TxOutsEmpty,
InputNotInUTxO,
OutputWithoutLovelace,
UnknownTxSize,
UnableToComputeFees,
FeesBelowMin,
MaxTxSizeExceeded,
UnableToProcessWitness,
MissingWitness,
WrongSignature,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ShelleyMAError {
TxInsEmpty,
InputNotInUTxO,
TTLExceeded,
AlonzoCompNotShelley,
UnknownTxSize,
MaxTxSizeExceeded,
ValueNotShelley,
MinLovelaceUnreached,
PreservationOfValue,
NegativeValue,
FeesBelowMin,
WrongEraOutput,
AddressDecoding,
WrongNetworkID,
MetadataHash,
MissingVKWitness,
MissingScriptWitness,
WrongSignature,
MintingLacksPolicy,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AlonzoError {
UnknownTxSize,
TxInsEmpty,
InputNotInUTxO,
CollateralNotInUTxO,
BlockExceedsValInt,
BlockPrecedesValInt,
ValIntUpperBoundMissing,
FeeBelowMin,
CollateralMissing,
TooManyCollaterals,
CollateralNotVKeyLocked,
AddressDecoding,
CollateralMinLovelace,
NonLovelaceCollateral,
NegativeValue,
PreservationOfValue,
MinLovelaceUnreached,
MaxValSizeExceeded,
OutputWrongNetworkID,
TxWrongNetworkID,
RedeemerMissing,
TxExUnitsExceeded,
MaxTxSizeExceeded,
VKWitnessMissing,
VKWrongSignature,
ReqSignerMissing,
ReqSignerWrongSig,
ScriptWitnessMissing,
MintingLacksPolicy,
InputDecoding,
UnneededNativeScript,
UnneededPlutusScript,
UnneededRedeemer,
DatumMissing,
UnneededDatum,
MetadataHash,
ScriptIntegrityHash,
}
pub type ValidationResult = Result<(), ValidationError>;

View file

@ -6,7 +6,7 @@ Starting at the root of the repository, simply go to *pallas-applying* and run `
## Explanations
### Byron
*pallas-applying/tests/byron.rs* contains multiple unit tests for validation on the Byron era.
*pallas-applying/tests/byron.rs* contains multiple unit tests for validation in the Byron era.
The first one, **suceessful_mainnet_tx**, is a positive unit test. It takes the CBOR of a mainnet transaction. Namely, the one whose hash is `a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26`, which can be viewed on Cardano Explorer [here](https://cexplorer.io/tx/a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26). Such a transaction has a single input which is added to the UTxO, prior to validation, by associating it to a transaction output sitting at its real (mainnet) address. This information was taken from Cardano Explorer as well, following the address link of the only input to the transaction, and taking its raw address CBOR content.
@ -21,7 +21,7 @@ Then comes a series of negative unit tests, namely:
- **wrong_signature** takes the mainnet transaction, alters the content of its witness, and calls validation on it.
### ShelleyMA
*pallas-applying/tests/byron.rs* contains multiple unit tests for validation on the ShelleyMA era, which is composed of the Shelley era itself as well as its two hardforks, Allegra and Mary.
*pallas-applying/tests/shelley_ma_.rs* contains multiple unit tests for validation in the ShelleyMA era, which is composed of the Shelley era itself as well as its two hardforks, Allegra and Mary.
Note that, since phase-1 validations do not include the execution of native scripts or any of their extensions introduced in Allegra and Mary, there is virtually no difference between the Shelley era and the Allegra hardfork. The Mary hardfork does however introduce features involved in phase-1 validations, namely checking whether the policies of all minted / burnt assets have a matching native script in the transaction witnesses set.
@ -45,3 +45,45 @@ List of negative unit tests:
- **missing_vk_witness** takes successful_mainnet_shelley_tx and removes the verification-key witness associated to one of its inputs.
- **vk_witness_changed** takes successful_mainnet_shelley_tx and modifies the verification-key witness associated to one of its inputs.
- **missing_native_script_witness** takes successful_mainnet_shelley_tx_with_script and removes the native script associated to one of its inputs.
### Alonzo
*pallas-applying/tests/alonzo.rs* contains multiple unit tests for validation in the Alonzo era.
Note that phase-1 validations do not include the execution of native scripts or Plutus scripts, which would correspond to phase-2 validations.
List of positive unit tests:
- **successful_mainnet_tx** ([here](https://cexplorer.io/tx/704b3b9c96f44cd5676e5dcb5dc0bb2555c66427625ccefe620101665da86868) to see on Cardano explorer) is a simple Alonzo transaction, with no native or Plutus scripts, no metadata, and no minting.
- **successful_mainnet_tx_with_plutus_script** ([here](https://cexplorer.io/tx/65160f403d2c7419784ae997d32b93a6679d81468af8173ccd7949df6704f7ba) to see on Cardano explorer) is an Alonzo transaction with a Plutus script (including the related data structure, like redeemers, datums, collateral inputs), but no metadata or minting.
- **successful_mainnet_tx_with_minting** ([here](https://cexplorer.io/tx/c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a) to see on Cardano Explorer) is an Alonzo transaction with metadata, but no native or Plutus scripts, and no minting.
- **successful_mainnet_tx_with_metadata** ([here](https://cexplorer.io/tx/8b6debb3340e5dac098ddb25fa647a99de12a6c1987c98b17ae074d6917dba16) to see on Cardano Explorer) is an Alonzo transaction containing metadata, but no scripts (native or Plutus) and no minting.
List of negative unit tests:
- **empty_ins** takes successful_mainnet_tx and removes its input.
- **unfound_utxo_input** takes successful_mainnet_tx and calls validation on it with an empty UTxO (which causes the input to be unfound).
- **validity_interval_lower_bound_unreached** takes sucessful_mainnet_tx and modifies its time interval in such a way that its validity time interval *lower* bound is located exactly one slot after the block slot.
- **validity_interval_upper_bound_surpassed** takes sucessful_mainnt_tx and modifies its time interval in such a way that its validity time interval *upper* bound is located exactly one slot before the block slot.
- **min_fees_unreached** submits validation on sucessful_mainnet_tx with an environment requesting the minimum fee to be higher than the one that the transaction actually paid.
- **no_collateral_inputs** takes successful_mainnet_tx_with_plutus_script and removes its collateral inputs before submitting the transaction for validation.
- **too_many_collateral_inputs** takes successful_mainnet_tx_with_plutus_script and submits its for validation with an environment allowing no collateral inputs.
- **collateral_is_not_verification_key_locked** takes sucessful_mainnet_tx_with_plutus_script and modifies the address of one of the collateral inputs to become a script-locked output instead of a verification-key-locked one.
- **collateral_with_other_assets** takes sucessful_mainnet_tx_with_plutus_script and adds non-lovelace assets to it.
- **collateral_without_min_lovelace** takes sucessful_mainnet_tx_with_plutus_script and submits it for validation with an environment requesting a higher lovelace percentage (when compared to the fee paid by the transaction) in collateral inputs than the actual amount paid by the transaction collateral.
- **preservation_of_value** modifies sucessful_mainnet_tx_with_plutus_script in such a way that the preservation-of-value equality does not hold.
- **output_network_ids** takes sucessful_mainnet_tx and modifies the network ID in the address of one of its outputs.
- **tx_network_id** takes sucessful_mainnet_tx and modifies its network ID.
- **tx_ex_units_exceeded** takes sucessful_mainnet_tx_with_plutus_script and validates it with an environment whose Plutus script execution values are below the needs of the transaction.
- **max_tx_size_exceeded** takes sucessful_mainnet_tx and validates it with an environment allowing only transactions whose size is lower than that of sucessful_mainnet_tx.
- **missing_required_signer** takes sucessful_mainnet_tx_with_plutus_script and submits it for validation after changing one of the required signers.
- **missing_vk_witness** removes a verification-key witness from sucessful_mainnet_tx befor submitting it for validation.
- **wrong_signature** modifies the signature of the verification-key witness in sucessful_mainnet_tx before trying to validate it.
- **missing_plutus_script** takes sucessful_mainnet_tx_with_plutus_script and removes its Plutus script before submitting it for validation.
- **extra_plutus_script** takes sucessful_mainnet_tx_with_plutus_script and adds a new, unneeded native script to its witness set.
- **minting_lacks_policy** takes sucessful_mainnet_tx_with_minting and removes the native script policy contained in it before submitting it for validation.
- **missing_input_datum** takes sucessful_mainnet_tx_with_plutus_script and removes the datum contained in its witness set.
- **extra_input_datum** takes sucessful_mainnet_tx_with_plutus_script and adds an unneded datum to its witness set.
- **extra_redeemer** takes sucessful_mainnet_tx_with_plutus_script and adds an unneeded redeemer to its witness set.
- **missing_redeemer** takes sucessful_mainnet_tx_with_plutus_script and removes its redeemer.
- **auxiliary_data_removed** takes sucessful_mainnet_tx_with_metadata and removes its auxiliary data (a.k.a. metadata).
- **min_lovelace_unreached** takes sucessful_mainnet_tx and submits validation on it with an environment requesting more lovelace on outputs than the amount actually paid by one of the outputs of the transaction.
- **max_val_exceeded** takes sucessful_mainnet_tx and submits validation on it with an environment disallowing value sizes as high as the size ofg one of the values in one of the transaction outputs of sucessful_mainnet_tx.
- **script_integrity_hash** takes sucessful_mainnet_tx_with_plutus_script and modifies the execution values of one of the redeemers in the witness set of the transaction, in such a way that all checks pass but the integrity hash of script-related data of the transaction is different from the script data hash contained in the body of the transaction.

File diff suppressed because it is too large Load diff

View file

@ -1,67 +1,41 @@
use std::{borrow::Cow, vec::Vec};
pub mod common;
use common::{cbor_to_bytes, minted_tx_payload_from_cbor, mk_utxo_for_byron_tx};
use pallas_applying::{
types::{
ByronError::*, ByronProtParams, Environment, FeePolicy, MultiEraProtParams,
ValidationError::*,
utils::{
ByronError, ByronProtParams, Environment, FeePolicy, MultiEraProtParams, ValidationError::*,
},
validate, UTxOs,
};
use pallas_codec::{
minicbor::{
bytes::ByteVec,
decode::{Decode, Decoder},
encode,
},
utils::{CborWrap, MaybeIndefArray, TagWrap},
utils::{CborWrap, MaybeIndefArray},
};
use pallas_primitives::byron::{Address, MintedTxPayload, Twit, Tx, TxIn, TxOut, Witnesses};
use pallas_traverse::{MultiEraInput, MultiEraOutput, MultiEraTx};
use pallas_primitives::byron::{MintedTxPayload, Twit, Tx, TxOut, Witnesses};
use pallas_traverse::MultiEraTx;
use std::vec::Vec;
#[cfg(test)]
mod byron_tests {
use super::*;
fn cbor_to_bytes(input: &str) -> Vec<u8> {
hex::decode(input).unwrap()
}
fn tx_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTxPayload<'a> {
pallas_codec::minicbor::decode::<MintedTxPayload>(&tx_cbor[..]).unwrap()
}
// Careful: this function assumes tx has exactly one input.
fn mk_utxo_for_single_input_tx<'a>(tx: &Tx, address_payload: String, amount: u64) -> UTxOs<'a> {
let mut tx_ins: Vec<TxIn> = tx.inputs.clone().to_vec();
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs");
let tx_in: TxIn = tx_ins.pop().unwrap();
let input_tx_out_addr: Address = match hex::decode(address_payload) {
Ok(addr_bytes) => Address {
payload: TagWrap(ByteVec::from(addr_bytes)),
crc: 3430631884,
},
_ => panic!("Unable to decode input address"),
};
let tx_out: TxOut = TxOut {
address: input_tx_out_addr,
amount,
};
let mut utxos: UTxOs = UTxOs::new();
add_to_utxo(&mut utxos, tx_in, tx_out);
utxos
}
#[test]
// Transaction hash:
// a9e4413a5fb61a7a43c7df006ffcaaf3f2ffc9541f54757023968c5a8f8294fd
fn successful_mainnet_tx_with_genesis_utxos() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron2.tx"));
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron2.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -86,12 +60,14 @@ mod byron_tests {
// a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26
fn successful_mainnet_tx() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -115,11 +91,13 @@ mod byron_tests {
// Identical to successful_mainnet_tx, except that all inputs are removed.
fn empty_ins() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let mut mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
// Clear the set of inputs in the transaction.
let mut tx: Tx = (*mtxp.transaction).clone();
@ -146,7 +124,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty"),
Err(err) => match err {
Byron(TxInsEmpty) => (),
Byron(ByronError::TxInsEmpty) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
@ -156,7 +134,7 @@ mod byron_tests {
// Identical to successful_mainnet_tx, except that all outputs are removed.
fn empty_outs() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mut mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
// Clear the set of outputs in the transaction.
let mut tx: Tx = (*mtxp.transaction).clone();
tx.outputs = MaybeIndefArray::Def(Vec::new());
@ -167,10 +145,12 @@ mod byron_tests {
};
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -187,7 +167,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Outputs set should not be empty"),
Err(err) => match err {
Byron(TxOutsEmpty) => (),
Byron(ByronError::TxOutsEmpty) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
@ -197,7 +177,7 @@ mod byron_tests {
// The transaction is valid, but the UTxO set is empty.
fn unfound_utxo() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = UTxOs::new();
let env: Environment = Environment {
@ -215,7 +195,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set"),
Err(err) => match err {
Byron(InputNotInUTxO) => (),
Byron(ByronError::InputNotInUTxO) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
@ -225,7 +205,7 @@ mod byron_tests {
// All lovelace in one of the outputs was removed.
fn output_without_lovelace() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mut mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
// Remove lovelace from output.
let mut tx: Tx = (*mtxp.transaction).clone();
let altered_tx_out: TxOut = TxOut {
@ -242,10 +222,12 @@ mod byron_tests {
};
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -262,7 +244,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All outputs must contain lovelace"),
Err(err) => match err {
Byron(OutputWithoutLovelace) => (),
Byron(ByronError::OutputWithoutLovelace) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
@ -272,12 +254,14 @@ mod byron_tests {
// Expected fees are increased by increasing the protocol parameters.
fn not_enough_fees() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -294,7 +278,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Fees should not be below minimum"),
Err(err) => match err {
Byron(FeesBelowMin) => (),
Byron(ByronError::FeesBelowMin) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
@ -304,12 +288,14 @@ mod byron_tests {
// Tx size limit set by protocol parameters is established at 0.
fn tx_size_exceeds_max() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -326,7 +312,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Transaction size cannot exceed protocol limit"),
Err(err) => match err {
Byron(MaxTxSizeExceeded) => (),
Byron(ByronError::MaxTxSizeExceeded) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
@ -336,7 +322,7 @@ mod byron_tests {
// The input to the transaction does not have a corresponding witness.
fn missing_witness() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mut mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
// Remove witness
let new_witnesses: Witnesses = MaybeIndefArray::Def(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
@ -346,10 +332,12 @@ mod byron_tests {
};
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -366,7 +354,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must have a witness signature"),
Err(err) => match err {
Byron(MissingWitness) => (),
Byron(ByronError::MissingWitness) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
@ -377,7 +365,7 @@ mod byron_tests {
// wrong.
fn wrong_signature() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let mut mtxp: MintedTxPayload = minted_tx_payload_from_cbor(&cbor_bytes);
// Modify signature in witness
let new_wit: Twit = match mtxp.witness[0].clone() {
Twit::PkWitness(CborWrap((pk, _))) => {
@ -395,10 +383,12 @@ mod byron_tests {
};
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_byron_tx(
&mtxp.transaction,
&[(
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
@ -415,16 +405,9 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Witness signature should verify the transaction"),
Err(err) => match err {
Byron(WrongSignature) => (),
Byron(ByronError::WrongSignature) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
}
// Helper functions.
fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: TxIn, tx_out: TxOut) {
let multi_era_in: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Owned(tx_in)));
let multi_era_out: MultiEraOutput = MultiEraOutput::Byron(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}

View file

@ -0,0 +1,96 @@
use pallas_applying::UTxOs;
use pallas_codec::{minicbor::bytes::ByteVec, utils::TagWrap};
use pallas_primitives::{
alonzo::{MintedTx, TransactionBody, TransactionOutput, Value},
byron::{Address, MintedTxPayload, Tx, TxOut},
};
use pallas_traverse::{MultiEraInput, MultiEraOutput};
use std::{borrow::Cow, iter::zip, vec::Vec};
use pallas_codec::utils::Bytes;
use pallas_crypto::hash::Hash;
pub fn cbor_to_bytes(input: &str) -> Vec<u8> {
hex::decode(input).unwrap()
}
pub fn minted_tx_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTx<'a> {
pallas_codec::minicbor::decode::<MintedTx>(&tx_cbor[..]).unwrap()
}
pub fn minted_tx_payload_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTxPayload<'a> {
pallas_codec::minicbor::decode::<MintedTxPayload>(&tx_cbor[..]).unwrap()
}
pub fn mk_utxo_for_byron_tx<'a>(tx: &Tx, tx_outs_info: &[(String, u64)]) -> UTxOs<'a> {
let mut utxos: UTxOs = UTxOs::new();
for (tx_in, (address_payload, amount)) in zip(tx.inputs.clone().to_vec(), tx_outs_info) {
let input_tx_out_addr: Address = match hex::decode(address_payload) {
Ok(addr_bytes) => Address {
payload: TagWrap(ByteVec::from(addr_bytes)),
crc: 3430631884,
},
_ => panic!("Unable to decode input address"),
};
let tx_out: TxOut = TxOut {
address: input_tx_out_addr,
amount: amount.clone(),
};
let multi_era_in: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Owned(tx_in)));
let multi_era_out: MultiEraOutput = MultiEraOutput::Byron(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}
utxos
}
pub fn mk_utxo_for_alonzo_compatible_tx<'a>(
tx_body: &TransactionBody,
tx_outs_info: &[(String, Value, Option<Hash<32>>)],
) -> UTxOs<'a> {
let mut utxos: UTxOs = UTxOs::new();
for (tx_in, (address, amount, datum_hash)) in zip(tx_body.inputs.clone(), tx_outs_info) {
let address_bytes: Bytes = match hex::decode(address) {
Ok(bytes_vec) => Bytes::from(bytes_vec),
_ => panic!("Unable to decode input address"),
};
let tx_out: TransactionOutput = TransactionOutput {
address: address_bytes,
amount: amount.clone(),
datum_hash: datum_hash.clone(),
};
let multi_era_in: MultiEraInput =
MultiEraInput::AlonzoCompatible(Box::new(Cow::Owned(tx_in)));
let multi_era_out: MultiEraOutput =
MultiEraOutput::AlonzoCompatible(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}
utxos
}
pub fn add_collateral<'a>(
tx_body: &TransactionBody,
utxos: &mut UTxOs<'a>,
collateral_info: &[(String, Value, Option<Hash<32>>)],
) {
match &tx_body.collateral {
Some(collaterals) => {
for (tx_in, (address, amount, datum_hash)) in zip(collaterals, collateral_info) {
let address_bytes: Bytes = match hex::decode(address) {
Ok(bytes_vec) => Bytes::from(bytes_vec),
_ => panic!("Unable to decode input address"),
};
let tx_out: TransactionOutput = TransactionOutput {
address: address_bytes,
amount: amount.clone(),
datum_hash: datum_hash.clone(),
};
let multi_era_in: MultiEraInput =
MultiEraInput::AlonzoCompatible(Box::new(Cow::Owned(tx_in.clone())));
let multi_era_out: MultiEraOutput =
MultiEraOutput::AlonzoCompatible(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}
}
None => panic!("Adding collateral to UTxO failed due to an empty list of collaterals"),
}
}

View file

@ -1,9 +1,10 @@
use std::borrow::Cow;
pub mod common;
use common::*;
use pallas_addresses::{Address, Network, ShelleyAddress};
use pallas_applying::{
types::{
Environment, FeePolicy, MultiEraProtParams, ShelleyMAError::*, ShelleyProtParams,
utils::{
Environment, FeePolicy, MultiEraProtParams, ShelleyMAError, ShelleyProtParams,
ValidationError::*,
},
validate, UTxOs,
@ -13,51 +14,17 @@ use pallas_codec::{
decode::{Decode, Decoder},
encode,
},
utils::Bytes,
utils::{Bytes, Nullable},
};
use pallas_crypto::hash::Hash;
use pallas_primitives::alonzo::{
MintedTx, MintedWitnessSet, TransactionBody, TransactionInput, TransactionOutput, VKeyWitness,
Value,
MintedTx, MintedWitnessSet, TransactionBody, TransactionOutput, VKeyWitness, Value,
};
use pallas_traverse::{Era, MultiEraInput, MultiEraOutput, MultiEraTx};
use pallas_traverse::{Era, MultiEraTx};
#[cfg(test)]
mod shelley_tests {
mod shelley_ma_tests {
use super::*;
fn cbor_to_bytes(input: &str) -> Vec<u8> {
hex::decode(input).unwrap()
}
fn minted_tx_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTx<'a> {
pallas_codec::minicbor::decode::<MintedTx>(&tx_cbor[..]).unwrap()
}
// Careful: this function assumes tx_body has exactly one input.
fn mk_utxo_for_single_input_tx<'a>(
tx_body: &TransactionBody,
address: String,
amount: Value,
datum_hash: Option<Hash<32>>,
) -> UTxOs<'a> {
let tx_ins: &Vec<TransactionInput> = &tx_body.inputs;
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs");
let tx_in: TransactionInput = tx_ins.first().unwrap().clone();
let address_bytes: Bytes = match hex::decode(address) {
Ok(bytes_vec) => Bytes::from(bytes_vec),
_ => panic!("Unable to decode input address"),
};
let tx_out: TransactionOutput = TransactionOutput {
address: address_bytes,
amount,
datum_hash,
};
let mut utxos: UTxOs = UTxOs::new();
add_to_utxo(&mut utxos, tx_in, tx_out);
utxos
}
#[test]
// Transaction hash:
// 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2
@ -65,11 +32,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -97,11 +66,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley2.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley2.address")),
Value::Coin(2000000),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -129,11 +100,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley3.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley3.address")),
Value::Coin(10000000),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -161,11 +134,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/mary1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Mary);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/mary1.address")),
Value::Coin(3500000),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -191,14 +166,16 @@ mod shelley_tests {
fn empty_ins() {
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 utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
// 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) {
@ -224,7 +201,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty"),
Err(err) => match err {
Shelley(TxInsEmpty) => (),
ShelleyMA(ShelleyMAError::TxInsEmpty) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -253,7 +230,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set"),
Err(err) => match err {
Shelley(InputNotInUTxO) => (),
ShelleyMA(ShelleyMAError::InputNotInUTxO) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -264,13 +241,15 @@ mod shelley_tests {
fn missing_ttl() {
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 utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
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) {
@ -296,7 +275,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "TTL must always be present in Shelley transactions"),
Err(err) => match err {
Shelley(AlonzoCompNotShelley) => (),
ShelleyMA(ShelleyMAError::AlonzoCompNotShelley) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -308,11 +287,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -330,7 +311,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "TTL cannot be exceeded"),
Err(err) => match err {
Shelley(TTLExceeded) => (),
ShelleyMA(ShelleyMAError::TTLExceeded) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -342,11 +323,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -364,7 +347,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Tx size exceeds max limit"),
Err(err) => match err {
Shelley(MaxTxSizeExceeded) => (),
ShelleyMA(ShelleyMAError::MaxTxSizeExceeded) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -377,11 +360,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -399,7 +384,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Output amount must be above min lovelace value"),
Err(err) => match err {
Shelley(MinLovelaceUnreached) => (),
ShelleyMA(ShelleyMAError::MinLovelaceUnreached) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -411,7 +396,7 @@ mod shelley_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) {
@ -421,11 +406,13 @@ mod shelley_tests {
mtx.transaction_body =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -443,7 +430,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Preservation of value property doesn't hold"),
Err(err) => match err {
Shelley(PreservationOfValue) => (),
ShelleyMA(ShelleyMAError::PreservationOfValue) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -455,11 +442,13 @@ mod shelley_tests {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -477,7 +466,7 @@ mod shelley_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Fee should not be below minimum"),
Err(err) => match err {
Shelley(FeesBelowMin) => (),
ShelleyMA(ShelleyMAError::FeesBelowMin) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -490,7 +479,7 @@ mod shelley_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 =
@ -533,33 +522,39 @@ mod shelley_tests {
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Output with wrong network ID should be rejected"),
Err(err) => match err {
Shelley(WrongNetworkID) => (),
ShelleyMA(ShelleyMAError::WrongNetworkID) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[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_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley3.address")),
Value::Coin(10000000),
None,
)],
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
@ -575,8 +570,11 @@ mod shelley_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(ShelleyMAError::MetadataHash) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
@ -588,7 +586,7 @@ mod shelley_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) {
@ -611,16 +609,18 @@ mod shelley_tests {
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Missing verification key witness"),
Err(err) => match err {
Shelley(MissingVKWitness) => (),
ShelleyMA(ShelleyMAError::MissingVKWitness) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -634,7 +634,7 @@ mod shelley_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();
@ -662,16 +662,18 @@ mod shelley_tests {
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
)],
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Missing verification key witness"),
Err(err) => match err {
Shelley(WrongSignature) => (),
ShelleyMA(ShelleyMAError::WrongSignature) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
@ -685,7 +687,7 @@ mod shelley_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) {
@ -708,26 +710,20 @@ mod shelley_tests {
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
let utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx(
&mtx.transaction_body,
&[(
String::from(include_str!("../../test_data/shelley2.address")),
Value::Coin(2000000),
None,
)],
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Missing native script witness"),
Err(err) => match err {
Shelley(MissingScriptWitness) => (),
ShelleyMA(ShelleyMAError::MissingScriptWitness) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
}
// Helper functions.
fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: TransactionInput, tx_out: TransactionOutput) {
let multi_era_in: MultiEraInput = MultiEraInput::AlonzoCompatible(Box::new(Cow::Owned(tx_in)));
let multi_era_out: MultiEraOutput =
MultiEraOutput::AlonzoCompatible(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}

View file

@ -1242,6 +1242,15 @@ pub struct Redeemer {
pub ex_units: ExUnits,
}
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
pub struct RedeemerPointer {
#[n(0)]
pub tag: RedeemerTag,
#[n(1)]
pub index: u32,
}
/* bootstrap_witness =
[ public_key : $vkey
, signature : $signature

View file

@ -0,0 +1 @@
018c9ae79bca586ac36dcfdbbf4d2826c685a6969411c338c14973cc7f7bdb37706cd03711fe64747f8cfcfd574c7445cc0378781e77a8cc00

1
test_data/alonzo1.tx Normal file
View file

@ -0,0 +1 @@
84a6008182582041d04edf6101abfc6d6f380e56f370580ec4df637b1883775aba62b8e37c7fc5010d80018282581d612e137a27a74aca6caff726fb9da65c371ad2d7f1cc8645648fcc11d11a05f8ddbe825839019af439b9b98b0e5d565f86382c31b6103a5cf63851d5b571a4d4571d7bdb37706cd03711fe64747f8cfcfd574c7445cc0378781e77a8cc001a56624ebb021a00028f6d031a02a31c410e80a1008182582093ba78df9ce14b8d927f0ced51b18f396b6a6dd8ccb272897b356a87dadb8d3a5840c50047bafa1adfbfd588d7c8be89f7ab17aecd47c4cc0ed5c1318caca57c8215d77d6878f0eb2bd2620b4ea552415a3028f98102275c9a564278d0f4e6425d02f5f6

View file

@ -0,0 +1 @@
714a59ebd93ea53d1bbf7f82232c7b012700a0cf4bb78d879dabb1a20a

View file

@ -0,0 +1 @@
01c81ffcbc08ff49965d74f90c391541ff1cc2b043ffe41c81d840be8729f2ae5ed49a1734823ba37fd09923f5f7d494ae0efa23dd98ce02da

View file

@ -0,0 +1 @@
01c81ffcbc08ff49965d74f90c391541ff1cc2b043ffe41c81d840be8729f2ae5ed49a1734823ba37fd09923f5f7d494ae0efa23dd98ce02da

View file

@ -0,0 +1 @@
01c81ffcbc08ff49965d74f90c391541ff1cc2b043ffe41c81d840be8729f2ae5ed49a1734823ba37fd09923f5f7d494ae0efa23dd98ce02da

View file

@ -0,0 +1 @@
01c81ffcbc08ff49965d74f90c391541ff1cc2b043ffe41c81d840be8729f2ae5ed49a1734823ba37fd09923f5f7d494ae0efa23dd98ce02da

View file

@ -0,0 +1 @@
01c81ffcbc08ff49965d74f90c391541ff1cc2b043ffe41c81d840be8729f2ae5ed49a1734823ba37fd09923f5f7d494ae0efa23dd98ce02da

View file

@ -0,0 +1 @@
01c81ffcbc08ff49965d74f90c391541ff1cc2b043ffe41c81d840be8729f2ae5ed49a1734823ba37fd09923f5f7d494ae0efa23dd98ce02da

View file

@ -0,0 +1 @@
01c81ffcbc08ff49965d74f90c391541ff1cc2b043ffe41c81d840be8729f2ae5ed49a1734823ba37fd09923f5f7d494ae0efa23dd98ce02da

1
test_data/alonzo2.tx Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
612e137a27a74aca6caff726fb9da65c371ad2d7f1cc8645648fcc11d1

1
test_data/alonzo3.tx Normal file
View file

@ -0,0 +1 @@
84a600818258205c34197488ebbb884443c24c804a8bb005d6eceaaf41721ea46f5322464ae560000182825839013bcac2810bd245df9f4863e75f0a2b48c4157b67e38019f88476fa5d949212c9ddb88132575ae4003c1a972646155ebeb6a89f51cee7982e821a00169b09a1581c91073759dc9eaff922791f6204d42749541e25d2c3e2d301925e8ffda14f4e46544c696e6b434e7847433533370182581d61e104dea1df6080d1bce10500ea9b114656797af53b9aa2962f2eecbc1a05dce8a9021a0004018c031a02f34b200758209c2ab25c5bdc6edbf70c254516489d1350e814f2c9bdfecc008d04439174548109a1581c91073759dc9eaff922791f6204d42749541e25d2c3e2d301925e8ffda14f4e46544c696e6b434e78474335333701a2008282582089ade7acc8bcc7364006415668a059d06b0da9791fd2585809e4d371acf186a158408dc433542e89db51f2ef734feea012705b4fcebc70c77566c074e03fe5d129347d0b7b8eed0201b360e28cad3dece0884acf1858302a7661e15b9444971cbe0a825820f5e24d984b6a4bfa2630ce1cb0cbc71a8cd9187fafdb23781894219f143aafaf584074d58d1606ea320d4a279376db047f9e8fac9b56fa625b0c0b77673ec65fb16fd91d9328a891606ba239d6f32be070302ac1b095ea75142c8af94abc2efeb908018182018282051a02f34b208200581c5542d21c44b5a4b4b197b86a4f17aedcb4e7201af43acebbb5ac5a2bf582a11902d1a178383931303733373539646339656166663932323739316636323034643432373439353431653235643263336532643330313932356538666664a16f4e46544c696e6b434e784743353337a86566696c657381a2696d656469615479706569766964656f2f6d7034637372637835697066733a2f2f516d56476e705450326b4b334842684468456d4c34674a7751766644776d3473413963784c33733137654574707165696d6167657835697066733a2f2f516d5934537343734342365170725631696666464b59676a6f677975337167754a4647763864654a5055533932786b696e707369726564206279781c43617264696f6c6f677920627920476f6f6420436861726c6f747465696d656469615479706569696d6167652f6769666b6d65726368616e646973656c4e46542d4c20486f6f646965646e616d6578234e46542d4c20436c6179204e6174696f6e202d2043617264696f6c6f677920233533376770726f6a656374781c436c6179204e6174696f6e207820476f6f6420436861726c6f747465657374796c656f4865617274206f6e20536c6565766580

View file

@ -0,0 +1 @@
01f64b141bfa7761c00a48a137b15d433af02c9275dbf52ea95566b59cb4f05ecc9fd8c9066ef7fd907db854c76caf6462b132ce133dc7cc44

1
test_data/alonzo4.tx Normal file
View file

@ -0,0 +1 @@
84a70081825820c514db191f8220a2603cee0c07adf7428a26ef9b44f08d2659a373da20bb144d010d80018282583901a548aefcaf1004185c48d0a9299e99c0f40afe59eb26da9d16d70c9252ff4e28d220bbd1b2c4661c9082bc169e4f59658078859bccf167bd1a0716549782583901a0526f3cf9c8cb3438532cdbd1f1739f4c8c50019d451fcd3308bfc6b4f05ecc9fd8c9066ef7fd907db854c76caf6462b132ce133dc7cc441ab91e1748021a0002a5c5031a02a31e570e80075820dc8240e5e3b815f383d2e43859a29481a875d7c6dcadef5e64a958a93a1b00c5a1008182582082b9f680f097c8bb43ced135459f4a5b11d6fb709b638d12c6f75fcfaf623e8558405476f3e15c89109a108a21cf85cb0144830656a234df6c655b493202f89e85257de1e76be5da6538bdf323b9c8b1559622d9ef9b6bb1b00c3ae6111e16a08502f5d90103a100a100784043354143363344373143463044393737363933373733383938443534314635454330423232323130434436424136393833434641363341334534463730333737